├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── ProofOfConcept.md ├── README.md ├── client ├── .npmrc ├── init ├── lib │ ├── README.md │ ├── connect-client.js │ ├── express-server.js │ ├── logger.js │ └── p2p-vps-server.js ├── package.json ├── rpi │ ├── docs │ │ └── firewall.md │ ├── flash-storage │ │ ├── .npmrc │ │ ├── README.md │ │ ├── device-config.json │ │ ├── lib │ │ │ ├── buildImage │ │ │ ├── cleanupImages │ │ │ ├── enterImage │ │ │ ├── prep-flash-storage │ │ │ ├── runImage │ │ │ ├── stopImage │ │ │ └── write-files.js │ │ ├── output-files │ │ │ ├── Dockerfile │ │ │ ├── config.json │ │ │ ├── dummyapp.js │ │ │ ├── finalsetup │ │ │ └── package.json │ │ ├── p2p-vps-client.js │ │ └── package.json │ └── simple │ │ ├── .npmrc │ │ ├── README.md │ │ ├── device-config.json │ │ ├── lib │ │ ├── buildImage │ │ ├── cleanupImages │ │ ├── enterImage │ │ ├── runImage │ │ ├── stopImage │ │ └── write-files.js │ │ ├── output-files │ │ ├── Dockerfile │ │ ├── config.json │ │ ├── connect-client.js │ │ ├── dummyapp.js │ │ ├── finalsetup │ │ └── package.json │ │ ├── p2p-vps-client.js │ │ └── package.json └── vm │ ├── README.md │ ├── flash-storage │ ├── .npmrc │ ├── README.md │ ├── device-config.json │ ├── lib │ │ ├── buildImage │ │ ├── cleanupImages │ │ ├── enterImage │ │ ├── prep-flash-storage │ │ ├── runImage │ │ ├── stopImage │ │ └── write-files.js │ ├── log │ │ └── README.md │ ├── output-files │ │ ├── Dockerfile │ │ ├── config.json │ │ ├── connect-client.js │ │ ├── dummyapp.js │ │ ├── example-Dockerfile │ │ ├── finalsetup │ │ └── package.json │ └── p2p-vps-client.js │ └── simple │ ├── .npmrc │ ├── README.md │ ├── device-config.json │ ├── lib │ ├── buildImage │ ├── cleanupImages │ ├── enterImage │ ├── runImage │ ├── stopImage │ └── write-files.js │ ├── output-files │ ├── Dockerfile │ ├── config.json │ ├── connect-client.js │ ├── dummyapp.js │ ├── example-Dockerfile │ ├── finalsetup │ └── package.json │ └── p2p-vps-client.js ├── images ├── federated-diagram.jpg ├── flash-client.jpg ├── marketplace-mockup.JPG ├── owned-devices-mockup.JPG ├── rental-mockup.JPG ├── simple-diagram.jpg ├── temp.jpg └── testimage.jpg ├── package-lock.json ├── package.json └── support └── Encrypting-Docker-containers-on-a-Virtual-Server.pdf /.eslintignore: -------------------------------------------------------------------------------- 1 | # /node_modules/* is a default rule, but only counts for the project-root 2 | **/node_modules/* 3 | **/coverage/* 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parserOptions": { 4 | "ecmaVersion": 8 5 | }, 6 | "plugins": ["prettier", "unicorn", "promise"], 7 | "rules": { 8 | "no-debugger": 0, 9 | "no-regex-spaces": ["error"], 10 | "no-unsafe-negation": ["error"], 11 | "curly": ["error", "multi-or-nest", "consistent"], 12 | "dot-location": ["error", "property"], 13 | "dot-notation": ["error"], 14 | "eqeqeq": ["error", "smart"], 15 | "no-else-return": ["error"], 16 | "no-extra-bind": ["error"], 17 | "no-extra-label": ["error"], 18 | "no-floating-decimal": ["error"], 19 | "no-implicit-coercion": ["error", { "allow": ["!!"] }], 20 | "wrap-iife": ["error", "inside"], 21 | "strict": ["error", "global"], 22 | "eol-last": ["error", "always"], 23 | "func-call-spacing": ["error", "never"], 24 | "comma-style": ["error", "last"], 25 | "comma-dangle": [ 26 | "error", 27 | { 28 | "arrays": "always-multiline", 29 | "objects": "always-multiline", 30 | "imports": "always-multiline", 31 | "exports": "always-multiline", 32 | "functions": "ignore" 33 | } 34 | ], 35 | "keyword-spacing": ["error"], 36 | "linebreak-style": ["error", "unix"], 37 | "new-parens": ["error"], 38 | "no-lonely-if": ["error"], 39 | "no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1 }], 40 | "no-trailing-spaces": ["error", { "ignoreComments": true }], 41 | "no-whitespace-before-property": ["error"], 42 | "semi": ["error", "always"], 43 | "template-tag-spacing": ["error", "never"], 44 | "arrow-body-style": 0, 45 | "arrow-parens": ["error", "as-needed"], 46 | "arrow-spacing": ["error"], 47 | "no-useless-computed-key": ["error"], 48 | "no-useless-rename": ["error"], 49 | "no-var": ["error"], 50 | "prefer-spread": ["error"], 51 | "prefer-template": ["error"], 52 | "rest-spread-spacing": ["error", "never"], 53 | "prefer-const": ["warn", { "destructuring": "all" }], 54 | "no-unreachable": ["warn"], 55 | "no-unused-vars": ["warn", { "args": "none" }], 56 | 57 | "prettier/prettier": [ 58 | "warn", 59 | { 60 | "printWidth": 100, 61 | "trailingComma": "es5" 62 | } 63 | ], 64 | 65 | "unicorn/filename-case": ["warn", { "case": "kebabCase" }], 66 | "unicorn/throw-new-error": ["error"], 67 | "unicorn/no-array-instanceof": ["error"], 68 | "unicorn/no-new-buffer": ["error"], 69 | "unicorn/no-hex-escape": ["error"], 70 | "unicorn/prefer-starts-ends-with": ["warn"], 71 | 72 | "promise/catch-or-return": ["error"] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | deviceGUID.json 3 | client.log 4 | output-files/* 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | The best way to start is to read through the [specification documents](specifications/high-level-overview.md). 2 | Next, introduce yourself on our [Gitter Channel](https://gitter.im/RPiOVN/Lobby). 3 | Finally, review the Trello boards and look for a task you can take on. 4 | The Trello boards are split up into the primary areas of the P2P VPS system: 5 | 6 | * [Marketplace](https://trello.com/b/U7aDKsSs/p2pvps-marketplace) 7 | * [Server](https://trello.com/b/1TgG5PFE/p2pvps-server) 8 | * [Client](https://trello.com/b/jqkUgHS5/p2pvps-client) 9 | 10 | To get code accepted into the repository, you need to fork the repository and submit a Pull Request (PR) with your code changes. 11 | Here is a rough step-by-step process on how to get started contributing to this project: 12 | 1. Fork this repository to your own account 13 | 2. Make any code changes that you'd like to see added to this repository. 14 | 3. Submit a Pull Request (PR) and link the PR to your forked repository. 15 | 4. An administrator of this project will review your Pull Request for acceptance into the main branch of the respository, or 16 | send you a note of additional changes that need to be made. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 P2P VPS Inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ProofOfConcept.md: -------------------------------------------------------------------------------- 1 | This page captures notes I took while experimenting with *reverse ssh* and *ssh port forwarding*. 2 | 3 | # Reverse SSH 4 | These instructions are adapted from [this tutorial](https://www.howtoforge.com/reverse-ssh-tunneling). They show the steps 5 | necessary to connect to a Raspberry Pi behind a series of firewalls by making a reverse SSH connection. 6 | 7 | **Notes:** 8 | * The raspberry pi is on my local network at 10.10.10.219 with username 'pi' 9 | * My internet-connected server is a Digital Ocean Droplet at IP 45.55.12.52 with username 'trout' 10 | 11 | **Steps** 12 | 1. On the RPi I run this command: 13 | 14 | `ssh -R 19999:localhost:22 trout@45.55.12.52` 15 | 16 | This will open an SSH connection between the RPi and the server. It will prompt me for the password to 'trout', 17 | and then log me in under that user on the server. 18 | 19 | 2. Open a separate SSH terminal directly to the server. From there, enter this command: 20 | 21 | `ssh -p 19999 pi@localhost` 22 | 23 | This will open an SSH connection between the server and the RPi inside its network. It will prompt me for the 24 | password to user 'pi'. 25 | 26 | 3. As long as the connection in step 1 remains established, I can open and close connections using step 2. 27 | 28 | 29 | # Port Forwarding using Reverse SSH 30 | Instructions below are pieced together from the following tutorials: 31 | * https://toic.org/blog/2009/reverse-ssh-port-forwarding/ 32 | * https://askubuntu.com/questions/50064/reverse-port-tunnelling 33 | 34 | **Steps:** 35 | 36 | 1. Enable Gateway ports in the SSHd server. 37 | * `sudo nano /etc/ssh/sshd_config` 38 | * At the bottom add this: `GatewayPorts clientspecified` 39 | * Save the file and restart sshd with this command: `/etc/init.d/ssh restart` 40 | 41 | 2. On the RPi, establish a reverse SSH connection with port forwarding with this command: 42 | `ssh -R 45.55.12.52:8000:localhost:80 trout@45.55.12.52` 43 | This forwards port 80 on the RPi to port 8000 on the server (45.55.12.52). It will request the password for user 'trout'. 44 | 45 | 3. Access the webpage being served by the RPi on port 80 by calling http://45.55.12.52:8000 46 | 47 | 48 | 49 | # Setting Up a Sub-Domain with NGINX 50 | This solution comes from this Stack Overflow thread: 51 | * http://stackoverflow.com/questions/23649444/redirect-subdomain-to-port-nginx-flask 52 | 53 | 1. Setup the GatewayPorts in the sshd_config file. 54 | 55 | 2. Setup a port 80 forwarding on the RPi to port 8080 on Droplet server: 56 | `ssh -R 107.170.227.211:8080:localhost:80 safeuser@107.170.227.211` 57 | 58 | 3. Add these lines to the nginx default file: 59 | ``` 60 | server { 61 | listen 80; 62 | server_name rpi.christroutner.com; 63 | 64 | location / { 65 | proxy_pass http://christroutner.com:8080; 66 | } 67 | } 68 | ``` 69 | 70 | 4. Check the syntax of the file with this command: 71 | `sudo nginx -t` 72 | 73 | 5. If the syntax checks out, reboot the nginx service: 74 | `sudo service nginx restart` 75 | 76 | 6. Point a browser at rpi.christroutner.com 77 | 78 | 79 | 80 | # Logging Into SSH Without a Password 81 | source: http://www.thegeekstuff.com/2008/11/3-steps-to-perform-ssh-login-without-password-using-ssh-keygen-ssh-copy-id 82 | 83 | 1. Create a user on the server. Assuming user '**user-ssh**' and server with IP **104.236.184.95** 84 | 2. On RPi: 'ssh-keygen' to generate key 85 | 3. On RPi: 'ssh-copy-id -i ~/.ssh/id_rsa.pub user-ssh@104.236.184.95' 86 | 4. Now log into server with 'ssh user-ssh@104.236.184.95' and a password should not be needed. 87 | 88 | 89 | 90 | # Next Steps: 91 | * Setup a subdomain on christroutner.com that points to an RPi. Proxy pass ports 80, 443, and 22. 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # P2P VPS - Client 2 | 3 | ## What is P2P VPS? 4 | P2P VPS aspires to create a peer-to-peer (P2P) marketplace offering Virtual 5 | Private Servers (VPS), similar to 6 | [Digital Ocean](http://digitalocean.com) or [Vultr](http://vultr.com). This 7 | would also be similar 8 | to co-location services like 9 | [Raspberry Pi Hosting](https://raspberry-hosting.com/en) or 10 | [Mythic Beasts](https://www.mythic-beasts.com/order/rpi). 11 | However, instead of using a data center, 12 | the marketplace would use an array of pseudo-anonymous IoT devices (like the 13 | [Raspberry Pi](https://www.raspberrypi.org/)) 14 | hosted by people participating in the marketplace. Anyone 15 | with an internet connection and a device capable of running 16 | [Docker](https://www.docker.com/) can rent 17 | that device in the P2P VPS marketplace. 18 | 19 | While [the applications of such a network](https://raspberry-hosting.com/en/applications) 20 | are similar, 21 | the P2P VPS marketplace will never have the speed or reliability of the 22 | commercial outfits linked above. Instead, the focus of P2P VPS is to create a 23 | decentralized network of anonymous web servers, capable of delivering websites 24 | or web apps, in order to prevent censorship and promote free speech. 25 | 26 | Members participating in the marketplace can earn cryptocurrency by renting out 27 | their device, while helping to create a more decentralized internet at the same time. 28 | That's the focus of the P2P VPS network. In this way, the P2P VPS software is 29 | censorship-fighting software similar to, but very different from, 30 | [TOR](https://www.torproject.org/). 31 | 32 | For more information, [read the full documentation](http://p2pvps.org/documentation/) 33 | 34 | ## About This Repository 35 | This repository is the *client-side* software needed to turn a computer into a VPS. 36 | By 'computer' we mean any computer. An old laptop, a Virtual Machine (VM) 37 | running on a desktop, or even an Internet of Things (IoT) device like a Raspberry 38 | Pi. Any computer capable of running Docker. The P2P VPS software is composed of 39 | three software packages: 40 | 41 | 1. *The Client* software runs on any computer and allows the device to be rented 42 | on the marketplace. 43 | 2. *The Server* software includes the database models, REST APIs, website content, 44 | and Vue.js marketplace app. 45 | 3. *Server Deployment* is a collection of Docker containers used to easily deploy 46 | copies of the Server. 47 | 48 | This repository contains **The Client**. 49 | 50 | ## Installation 51 | 52 | * This repository currently targets two different types of hardware: 53 | One targets the [Raspberry Pi](client/rpi). 54 | The other targets Ubuntu 16.04 environment running in a [VirtualBox VM](client/vm). 55 | 56 | * Each target of the Client has two versions. The *simple client* is the simplest 57 | possible client. You should install this client first before moving on to the more 58 | complex *flash client*. The flash client builds on top of the simple client by 59 | adding sudo privileges and persistent storage. 60 | 61 | * Installation Instructions: 62 | * [Raspberry Pi simple-client](client/rpi/simple) 63 | * [Raspberry Pi flash-client](client/rpi/flash-storage) 64 | * [VM simple-client](client/vm/simple) 65 | * [VM flash-client](client/vm/flash-storage) 66 | 67 | ### File Layout 68 | * The `client` directory contains various implementations of the P2P VPS Client 69 | code, targeting different hardware. Within each implementation are the following 70 | sub-directories: 71 | 72 | * The `simple` directory creates the simplest possible client. It will 73 | set up a reverse SSH connection with no persistent storage and the user will 74 | not have sudo privileges. It's the simplest, most limited, way to create a VPS 75 | that can be rented out on P2P VPS. This client is primarily used for testing. 76 | 77 | *It is best to build this client first, before moving on to a more complex 78 | version of the client.* 79 | 80 | * the `flash-storage` directory creates a more complex Client by adding sudo 81 | privileges and persistent storage. 82 | 83 | ## License 84 | (The MIT License) 85 | 86 | Copyright (c) 2018 [Chris Troutner](http://christroutner.com) and [P2P VPS Inc.](http://p2pvps.org) 87 | 88 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 89 | 90 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 91 | 92 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 93 | -------------------------------------------------------------------------------- /client/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock = false 2 | -------------------------------------------------------------------------------- /client/init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | mkdir ~/.p2pvps 3 | mkdir ~/.p2pvps/logs 4 | mkdir ~/.p2pvps/logs/connect-client 5 | -------------------------------------------------------------------------------- /client/lib/README.md: -------------------------------------------------------------------------------- 1 | This directory contains shared libraries used by each of the clients. 2 | 3 | TODO: 4 | * Expand the deviceGUID.json file to include config info needed by the client to connect to a server. 5 | * Change `support_files` directory to `output-files` 6 | * Move the config.json file to the new `output-files` directory. 7 | * Client specific files go into the internal lib directory. Generic functions get moved to the `lib` 8 | directory in the parent `client` directory. 9 | * Try to move the shell scripts to the local `lib` folder. Need to see if I can still execute them with 10 | `execa`. 11 | -------------------------------------------------------------------------------- /client/lib/connect-client.js: -------------------------------------------------------------------------------- 1 | /* 2 | * The primary functions of this program are to: 3 | * -Establish a reverse SSH tunnel with the SSH server 4 | * -Run a heartbeat API that updates a timestamp on the P2P VPS server every 2 minutes. 5 | */ 6 | 7 | "use strict"; 8 | 9 | const tunnel = require("reverse-tunnel-ssh"); //tunnel is a ssh2 clientConnection object 10 | const request = require("request"); //Used for CURL style requests. 11 | const express = require("express"); 12 | const winston = require("winston"); 13 | 14 | global.config = false; 15 | 16 | try { 17 | global.config = require("./config.json"); 18 | winston.info(`Connecting device to P2P VPS server with ID ${global.config.deviceId}`); 19 | } catch (err) { 20 | console.error("Could not open the config.json file!", err); 21 | process.exit(1); 22 | } 23 | 24 | // Set up the Winston logging. 25 | winston.add(winston.transports.File, { 26 | filename: "/usr/src/app/logs/connect-client.log", 27 | maxFiles: 1, 28 | colorize: false, 29 | timestamp: true, 30 | datePattern: ".yyyy-MM-ddTHH-mm", 31 | maxsize: 1000000, 32 | json: false, 33 | }); 34 | 35 | // Set the logging level. 36 | winston.level = "debug"; 37 | 38 | // Start first line of the log. 39 | const now = new Date(); 40 | winston.log("info", `Application starting at ${now}`); 41 | 42 | const app = express(); 43 | const port = 4010; 44 | 45 | /* 46 | * Use Handlebars for templating 47 | */ 48 | const exphbs = require("express3-handlebars"); 49 | //let hbs; 50 | 51 | // For gzip compression 52 | //app.use(express.compress()); 53 | 54 | /* 55 | * Config for Production and Development 56 | */ 57 | app.engine( 58 | "handlebars", 59 | exphbs({ 60 | // Default Layout and locate layouts and partials 61 | defaultLayout: "main", 62 | layoutsDir: "views/layouts/", 63 | partialsDir: "views/partials/", 64 | }) 65 | ); 66 | 67 | // Locate the views 68 | app.set("views", `${__dirname}/views`); 69 | 70 | // Locate the assets 71 | app.use(express.static(`${__dirname}/assets`)); 72 | 73 | // Set Handlebars 74 | app.set("view engine", "handlebars"); 75 | 76 | // Index Page 77 | app.get("/", function(request, response, next) { 78 | response.render("index"); 79 | }); 80 | 81 | /* Start up the Express web server */ 82 | app.listen(process.env.PORT || port); 83 | winston.info(`P2P VPS Keep Alive timer started on port ${port}`); 84 | 85 | // Check in with the P2P VPS server every 2 minutes with this API. 86 | // This lets the server know the Client is still connected. 87 | const checkInTimer = setInterval(function() { 88 | //Register with the server by sending the benchmark data. 89 | request.get( 90 | { 91 | url: `http://${global.config.serverIp}:${global.config.serverPort}/api/client/checkin/${ 92 | global.config.deviceId 93 | }`, 94 | // form: obj 95 | }, 96 | function(error, response, body) { 97 | try { 98 | debugger; 99 | //If the request was successfull, the server will respond with username, password, and port to be 100 | //used to build the Docker file. 101 | if (!error && response.statusCode === 200) { 102 | debugger; 103 | 104 | //Convert the data from a string into a JSON object. 105 | const data = JSON.parse(body); //Convert the returned JSON to a JSON string. 106 | 107 | if (data.success) { 108 | const now = new Date(); 109 | winston.info( 110 | `Checked in for device ${global.config.deviceId} at ${now.toLocaleString()}` 111 | ); 112 | } else { 113 | console.error(`Check-in failed for ${global.config.deviceId}`); 114 | } 115 | } else { 116 | debugger; 117 | 118 | // Server responded with some other status than 200. 119 | if (response) { 120 | if (response.statusCode !== 200) 121 | console.error("P2P VPS server rejected checking: ", response); 122 | } else if (error.code === "EHOSTUNREACH" || error.code === "ECONNREFUSED") { 123 | // Could not connect to the server. 124 | debugger; 125 | winston.info(`Warning: Could not connect to server at ${now.toLocaleString()}`); 126 | return; 127 | } else { 128 | console.error( 129 | "Server responded with error when trying to register the device: ", 130 | error 131 | ); 132 | console.error( 133 | "Ensure the ID in your deviceGUID.json file matches the ID in the Owned Devices section of the marketplace." 134 | ); 135 | } 136 | } 137 | } catch (err) { 138 | winston.info(`connect-client.js exiting with error: ${err}`); 139 | } 140 | } 141 | ); 142 | }, 120000); 143 | 144 | // Establish a reverse SSH connection. 145 | function createTunnel() { 146 | try { 147 | const conn = tunnel( 148 | { 149 | host: global.config.sshServer, 150 | port: global.config.sshServerPort, //The SSH port on the server. 151 | username: "sshuser", 152 | password: "sshuserpassword", 153 | dstHost: "0.0.0.0", // bind to all IPv4 interfaces 154 | dstPort: global.config.sshTunnelPort, //The new port that will be opened 155 | //srcHost: '127.0.0.1', // default 156 | srcPort: 3100, // The port on the Pi to tunnel to. 157 | //readyTimeout: 20000, 158 | //debug: myDebug, 159 | }, 160 | function(error, clientConnection) { 161 | if (error) { 162 | winston.info("There was an error in connect-client.js/tunnel()!"); 163 | console.error(JSON.stringify(error, null, 2)); 164 | } else { 165 | winston.info( 166 | `Reverse tunnel established on destination port ${global.config.sshTunnelPort}` 167 | ); 168 | } 169 | } 170 | ); 171 | 172 | conn.on("error", function(error) { 173 | debugger; 174 | 175 | // Could not connect to the internet. 176 | if (error.level === "client-timeout" || error.level === "client-socket") { 177 | debugger; 178 | winston.info("Warning, could not connect to SSH server. Waiting before retry."); 179 | } else { 180 | console.error("Error with connect-client.js: "); 181 | console.error(JSON.stringify(error, null, 2)); 182 | } 183 | 184 | // Try again in a short while. 185 | setTimeout(function() { 186 | createTunnel(); 187 | }, 30000); 188 | }); 189 | 190 | conn.on("close", function(val) { 191 | winston.info("SSH connection for connect-client.js was closed."); 192 | }); 193 | 194 | conn.on("end", function(val) { 195 | winston.info("SSH connection for connect-client ended."); 196 | }); 197 | } catch (err) { 198 | console.error("I caught the error!"); 199 | console.error(JSON.stringify(err, null, 2)); 200 | } 201 | } 202 | 203 | // Execute the first time. 204 | setTimeout(function() { 205 | createTunnel(); 206 | }, 5000); 207 | 208 | function myDebug(val) { 209 | winston.info(val); 210 | } 211 | -------------------------------------------------------------------------------- /client/lib/express-server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 RPiOVN.org 3 | * Licensing Information: MIT License 4 | * 5 | * This library file handle communication with the P2P VPS server. 6 | */ 7 | 8 | "use strict"; 9 | 10 | // Libraries 11 | const express = require("express"); 12 | 13 | // Globals 14 | 15 | class ExpressServer { 16 | constructor(app, port) { 17 | this.app = app; 18 | this.port = port; 19 | } 20 | 21 | start() { 22 | // Use Handlebars for templating 23 | const exphbs = require("express3-handlebars"); 24 | let hbs; 25 | 26 | // For gzip compression 27 | //app.use(express.compress()); 28 | 29 | //Config for Production and Development 30 | this.app.engine( 31 | "handlebars", 32 | exphbs({ 33 | // Default Layout and locate layouts and partials 34 | defaultLayout: "main", 35 | layoutsDir: "views/layouts/", 36 | partialsDir: "views/partials/", 37 | }) 38 | ); 39 | 40 | // Locate the views 41 | this.app.set("views", `${__dirname}/views`); 42 | 43 | // Locate the assets 44 | this.app.use(express.static(`${__dirname}/assets`)); 45 | 46 | // Set Handlebars 47 | this.app.set("view engine", "handlebars"); 48 | 49 | // Index Page 50 | this.app.get("/", function(request, response, next) { 51 | response.render("index"); 52 | }); 53 | 54 | // Start up the Express web server 55 | this.app.listen(process.env.PORT || this.port).on("error", expressErrorHandler); 56 | //console.log('Express started on port ' + port); 57 | 58 | // Handle generic errors thrown by the express application. 59 | function expressErrorHandler(err) { 60 | if (err.code === "EADDRINUSE") 61 | console.error(`Port ${this.port} is already in use. Is this program already running?`); 62 | else console.error(JSON.stringify(err, null, 2)); 63 | 64 | console.error("Express could not start!"); 65 | process.exit(0); 66 | } 67 | } 68 | } 69 | 70 | module.exports = ExpressServer; 71 | -------------------------------------------------------------------------------- /client/lib/logger.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Chris Troutner & P2PVPS.org 3 | * Licensing Information: MIT License 4 | * 5 | * This library file to handle logging on the Client. 6 | */ 7 | 8 | "use strict"; 9 | 10 | // Libraries 11 | const winston = require("winston"); 12 | 13 | // Globals 14 | 15 | class Logger { 16 | constructor(deviceConfig) { 17 | console.log(`Initializing Winston`); 18 | 19 | // Set up the Winston logging. 20 | winston.add(winston.transports.File, { 21 | filename: deviceConfig.loggingPath, 22 | maxFiles: 1, 23 | colorize: false, 24 | timestamp: true, 25 | datePattern: ".yyyy-MM-ddTHH-mm", 26 | maxsize: 1000000, 27 | json: false, 28 | }); 29 | 30 | // Set the logging level. 31 | winston.level = "debug"; 32 | 33 | // Start first line of the log. 34 | const now = new Date(); 35 | winston.log("info", `Application starting at ${now}`); 36 | } 37 | 38 | info(log) { 39 | winston.log("info", log); 40 | } 41 | 42 | log(log) { 43 | this.info(log); 44 | } 45 | 46 | debug(log) { 47 | winston.log("debug", log); 48 | } 49 | 50 | error(log, ...args) { 51 | console.error(log, args); 52 | winston.error(log, args); 53 | } 54 | 55 | warn(log, ...args) { 56 | console.warn(log, ...args); 57 | winston.warn(log, ...args); 58 | } 59 | } 60 | 61 | module.exports = Logger; 62 | -------------------------------------------------------------------------------- /client/lib/p2p-vps-server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Chris Troutner & P2P VPS Inc. 3 | * Licensing Information: MIT License 4 | * 5 | * This library file handle communication with the P2P VPS server. 6 | */ 7 | 8 | "use strict"; 9 | 10 | // Libraries 11 | //const fs = require("fs"); 12 | const request = require("request"); //Used for CURL requests. 13 | const rp = require("request-promise"); 14 | const execa = require("execa"); 15 | const getStream = require("get-stream"); 16 | let logr; 17 | 18 | // Globals 19 | //let globalThis; //Used in functions below when 'this' loses context. 20 | const CHECK_EXPIRATION_PERIOD = 60000 * 6; 21 | let checkExpirationTimer; 22 | 23 | class P2pVpsServer { 24 | constructor(deviceConfig, logger) { 25 | this.deviceId = deviceConfig.deviceId; 26 | this.serverIp = deviceConfig.serverIp; 27 | this.serverPort = deviceConfig.serverPort; 28 | this.sshServer = deviceConfig.sshServer; 29 | this.sshServerPort = deviceConfig.sshServerPort; 30 | 31 | // Copy handle to logging system to higher scoped variable. 32 | logr = logger; 33 | } 34 | 35 | register(config) { 36 | return new Promise((resolve, reject) => { 37 | //debugger; 38 | 39 | //Register with the server by sending the benchmark data. 40 | request.post( 41 | { 42 | url: `http://${this.serverIp}:${this.serverPort}/api/client/register/${this.deviceId}`, 43 | form: config.deviceSpecs, 44 | }, 45 | function(error, response, body) { 46 | try { 47 | //If the request was successfull, the server will respond with username, password, and port to be 48 | //used to build the Docker file. 49 | if (!error && response.statusCode === 200) { 50 | //Convert the data from a string into a JSON object. 51 | const data = JSON.parse(body); //Convert the returned JSON to a JSON string. 52 | 53 | //console.log(`data: ${JSON.stringify(data, null, 2)}`); 54 | console.log(`Username: ${data.device.username}`); 55 | console.log(`Password: ${data.device.password}`); 56 | console.log(`Port: ${data.device.port}`); 57 | 58 | return resolve(data); 59 | } 60 | 61 | if (error) { 62 | if (error.code === "EHOSTUNREACH") { 63 | console.error("Could not connect to server! Exiting."); 64 | } else if (error.code === "ECONNREFUSED") { 65 | console.error( 66 | "Server could not establish a connection. It may be down temporarily. Try again later." 67 | ); 68 | } 69 | } else { 70 | console.error( 71 | "Server responded with error when trying to register the device: ", 72 | error 73 | ); 74 | console.error( 75 | "Ensure the ID in your deviceGUID.json file matches the ID in the Owned Devices section of the marketplace." 76 | ); 77 | console.log(JSON.stringify(error, null, 2)); 78 | } 79 | 80 | process.exit(1); // Exit the application. 81 | } catch (err) { 82 | console.log(`p2p-vps-server.js/register() exiting with error: ${err}`); 83 | } 84 | } 85 | ); 86 | }); 87 | } 88 | 89 | // This function returns a devicePublicModel given the deviceId. 90 | getDevicePublicModel(deviceId) { 91 | //debugger; 92 | 93 | const options = { 94 | method: "GET", 95 | uri: `http://${this.serverIp}:${this.serverPort}/api/device/${deviceId}`, 96 | json: true, // Automatically stringifies the body to JSON 97 | }; 98 | 99 | return rp(options).then(function(data) { 100 | //debugger; 101 | console.log(`data: ${JSON.stringify(data, null, 2)}`); 102 | if (data.collection === undefined) throw `No devicePublicModel with ID of ${deviceId}`; 103 | 104 | return data.collection; 105 | }); 106 | } 107 | 108 | // This function returns a devices expiration date given the deviceId. 109 | getExpiration(deviceId) { 110 | //debugger; 111 | 112 | const options = { 113 | method: "GET", 114 | uri: `http://${this.serverIp}:${this.serverPort}/api/client/expiration/${deviceId}`, 115 | json: true, // Automatically stringifies the body to JSON 116 | }; 117 | 118 | return rp(options).then(function(data) { 119 | //debugger; 120 | 121 | if (data.expiration === undefined) 122 | throw `Could not retrieve expiration for device ${deviceId}`; 123 | 124 | return new Date(data.expiration); 125 | }); 126 | } 127 | 128 | startExpirationTimer(registerFunc) { 129 | //console.log(`p2p-vps-server.js/startExpirationTime(registerFunc): ${typeof registerFunc}`); 130 | 131 | if (registerFunc === undefined) throw "register() function not defined."; 132 | 133 | checkExpirationTimer = setInterval(() => { 134 | //console.log(`p2p-vps-server.js/setInterval(registerFunc): ${typeof registerFunc}`); 135 | this.checkExpiration(registerFunc); 136 | }, CHECK_EXPIRATION_PERIOD); 137 | } 138 | 139 | // This function is called by a timer after the Docker contain has been successfully 140 | // launched. 141 | checkExpiration(registerFunc) { 142 | debugger; 143 | //console.log(`p2p-vps-server.js/checkExpiration(registerFunc): ${typeof registerFunc}`); 144 | 145 | if (registerFunc === undefined) throw "register() function not defined."; 146 | 147 | const now = new Date(); 148 | logr.log(`checkExpiration() running at ${now}`); 149 | 150 | // Get the expiration date for this device from the server. 151 | this.getExpiration(this.deviceId) 152 | // Check expiration date. 153 | .then(expiration => { 154 | //const now = new Date(); 155 | 156 | logr.log(`Expiration date: ${expiration}`); 157 | logr.debug(`Expiration type: ${typeof expiration}`); 158 | 159 | const expirationDate = new Date(expiration); 160 | 161 | // If the expiration date has been reached 162 | if (expirationDate.getTime() < now.getTime()) { 163 | // Stop the docker container. 164 | logr.log("Stopping the docker container"); 165 | const stream = execa("./lib/stopImage").stdout; 166 | 167 | stream.pipe(process.stdout); 168 | 169 | return ( 170 | getStream(stream) 171 | // Clean up any orphaned docker images. 172 | .then(output => { 173 | const stream2 = execa("./lib/cleanupImages").stdout; 174 | 175 | stream2.pipe(process.stdout); 176 | 177 | return getStream(stream2); 178 | }) 179 | 180 | // Reregister the device. 181 | .then(output => { 182 | debugger; 183 | clearInterval(checkExpirationTimer); // Stop the timer. 184 | 185 | //registerDevice(); // Re-register the device with the server. 186 | //console.log( 187 | // `p2p-vps-server.js/startExpirationTime2(registerFunc): ${typeof registerFunc}` 188 | //); 189 | registerFunc(); 190 | }) 191 | ); 192 | } 193 | }) 194 | 195 | .catch(err => { 196 | debugger; 197 | logr.error("Error in checkExpiration(): "); 198 | 199 | if (err.error) { 200 | if (err.statusCode >= 500 || err.name === "RequestError") { 201 | logr.error("Connection to the server was refused. Will try again."); 202 | } else { 203 | debugger; 204 | logr.error(`Error stringified: ${JSON.stringify(err, null, 2)}`); 205 | } 206 | } 207 | }); 208 | } 209 | } 210 | 211 | module.exports = P2pVpsServer; 212 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "connect-client.js", 3 | "version": "1.0.1", 4 | "description": "Connects the Client to the P2P VPS server", 5 | "main": "connectClient.js", 6 | "dependencies": { 7 | "execa": "^0.8.0", 8 | "express": "^4.16.2", 9 | "express3-handlebars": "^0.5.2", 10 | "get-stream": "^3.0.0", 11 | "node-promise": "^0.5.12", 12 | "p2pvps-sudo": "^1.0.4", 13 | "request": "^2.83.0", 14 | "request-promise": "^4.2.2", 15 | "reverse-tunnel-ssh": "^1.1.0", 16 | "sudo": "^1.0.3", 17 | "winston": "^2.4.0" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/P2PVPS/p2pvps-client" 22 | }, 23 | "devDependencies": {}, 24 | "scripts": { 25 | "test": "echo \"Error: no test specified\" && exit 1" 26 | }, 27 | "author": "chris.troutner@gmail.com", 28 | "license": "MIT" 29 | } 30 | -------------------------------------------------------------------------------- /client/rpi/docs/firewall.md: -------------------------------------------------------------------------------- 1 | ## Docker Firewall 2 | The instructions below describe how to setup a firewall so that the Docker containers 3 | on an RPi are not allowed to speak with other devices on the LAN. This prevents 4 | renters of the device from being able to hack other devices on the local network. 5 | The instructions below are based on 6 | [this blog entry](https://blog.daknob.net/debian-firewall-docker/). 7 | 8 | **This firewall assumes that you are using the Ethernet port on the RPi to connect 9 | it to the network, and not the WiFi antenna.** 10 | 11 | * View the iptable rules created by Docker by running the command: 12 | `sudo iptables -L` 13 | 14 | * Create the directory `/etc/systemd/system/docker.service.d`. 15 | 16 | * Enter that directory and create the file `noiptables.conf`. 17 | 18 | * Add the following content to this file with `nano`: 19 | 20 | ``` 21 | [Service] 22 | ExecStart= 23 | ExecStart=/usr/bin/dockerd -H fd:// --iptables=false --dns=8.8.4.4 24 | ``` 25 | 26 | * Reboot the Raspberry Pi to let the changes take effect. 27 | 28 | * Run the command `sudo iptables -L` and verify that the iptable rules created 29 | by Docker are no longer there. 30 | 31 | * Install the following package: 32 | 33 | `apt-get install iptables-persistent` 34 | 35 | * Replace the `/etc/iptables/rules.v4` file with the following: 36 | 37 | ``` 38 | *nat 39 | :PREROUTING ACCEPT [0:0] 40 | :INPUT ACCEPT [0:0] 41 | :OUTPUT ACCEPT [0:0] 42 | :POSTROUTING ACCEPT [0:0] 43 | -A POSTROUTING -o eth0 -j MASQUERADE 44 | COMMIT 45 | 46 | # Generated by iptables-save v1.6.0 on Wed Jan 24 08:06:27 2018 47 | *filter 48 | :INPUT ACCEPT [0:0] 49 | :FORWARD ACCEPT [0:0] 50 | :OUTPUT ACCEPT [0:0] 51 | 52 | # Allow localhost 53 | -A INPUT -i lo -j ACCEPT 54 | -A OUTPUT -o lo -j ACCEPT 55 | 56 | # ICMP 57 | -A INPUT -p icmp -j ACCEPT 58 | 59 | # Docker 60 | ## Prevent docker from speaking directly to any devices on the LAN 61 | -A FORWARD -i docker0 -d 192.168.0.0/16 -j DROP 62 | ## Allow all other traffic to pass through 63 | -A FORWARD -i docker0 -o eth0 -j ACCEPT 64 | -A FORWARD -i eth0 -o docker0 -j ACCEPT 65 | 66 | # Incoming 67 | -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 68 | ## Allow DNS to pass through 69 | -A INPUT -p udp -i eth0 --sport 53 -j ACCEPT 70 | 71 | # Outgoing 72 | ## Allow DNS to pass through 73 | -A OUTPUT -p udp -o eth0 --dport 53 -j ACCEPT 74 | ## Do not allow host to speak to LAN devices, in case it gets hacked. 75 | #-A OUTPUT -d 192.168.0.0/16 -j DROP 76 | -A OUTPUT -j ACCEPT 77 | 78 | # Routing 79 | -A FORWARD -j ACCEPT 80 | 81 | COMMIT 82 | ``` 83 | 84 | * Load the new firewall rules with this command: 85 | 86 | `sudo netfilter-persistent reload` 87 | -------------------------------------------------------------------------------- /client/rpi/flash-storage/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock = false 2 | -------------------------------------------------------------------------------- /client/rpi/flash-storage/README.md: -------------------------------------------------------------------------------- 1 | # Renter Shell with Flash Storage 2 | The code in this directory allows device Owners to create a Client Device with 3 | persistent storage. Also, unlike the [simple client](../simple), the Renter has 4 | sudo privileges in this client. *However, it is much easier to debug issues if 5 | you build the [simple shell](../simple) **before** building this client.* 6 | 7 | It is **strongly** recommended that device 8 | owners do not use the SD card for persistent storage, and instead use a dedicated 9 | USB flash drive. The scripts in this directory assume this configuration. 10 | Reliable **64 GB** flash drives can now be found on [Amazon.com](http://amzn.to/2CZq2eR) 11 | for under $15. This capacity will be considered the 'standard' storage size for a P2P VPS client. 12 | 13 | ![flash client](../../../images/flash-client.jpg?raw=true "flash client") 14 | 15 | 16 | ## Installation 17 | To prepare your device for the P2P VPS marketplace with this shell, run the 18 | following commands: 19 | 20 | 1. Follow steps 1 through 11 for the [simple client](../simple). 21 | 22 | 2. Create a new mount point with this command: 23 | 24 | `sudo mkdir /media/usb` 25 | 26 | 3. By default, flash drives plugged into the Raspberry Pi's USB port are identified 27 | as */dev/sda1* by the operating system. Run the following command and note the 28 | `PARTUUID` value: 29 | 30 | `sudo blkid` 31 | 32 | output example: /dev/sda1: UUID="8dd06116-a29c-459f-9002-c1cccd7892d5" TYPE="ext4" PARTUUID="eb5e7935-01" 33 | 34 | 4. Add the following line to `/etc/fstab` with the command `sudo nano /etc/fstab`. Replace 35 | the UUID value with the value from your own device. 36 | Note: You can brick your RPi if this line is malformed. This would require re-loading NOOBS 37 | and installing Raspbian. So make sure you type it correctly: 38 | 39 | `/dev/sda1 /media/usb ext4 defaults 0 0` 40 | 41 | 5. Reboot the RPi. When the device reboots, the flash drive will automatically be mounted 42 | to `/media/usb`. 43 | 44 | 6. Prepare the USB flash drive for use by formatting and mounting it. 45 | Ensure you are in the `p2pvps-client/client/rpi/flash-storage` directory. 46 | Run the flash preparation script with this command: 47 | 48 | `./lib/prep-flash-storage` 49 | 50 | 7. Install Wondershaper, which will allow you bandwidth limit the rental container. 51 | 52 | `sudo apt-get install -y wondershaper` 53 | 54 | 8. Install library dependencies with this command: 55 | 56 | `npm install` 57 | 58 | 9. Get your device GUID from the P2P VPS marketplace. This is provided in 59 | the *Owned Devices view* by clicking the *+Add New Device* button. Paste this GUID into the `device-config.json` file. 60 | 61 | 10. Launch the flash client. The first time will take a while as it will need 62 | to download and build several Docker containers: 63 | 64 | `node p2p-vps-client.js` 65 | 66 | That's it! Once the application presents the message `Docker image has been built and is running.`, 67 | your device is now connected to the P2P VPS server and listed on the market for rent. 68 | 69 | 11. If you've successfully gotten this far, the next step is to get the software to 70 | start on bootup. First, stop the programs you just started by hitting Ctrl-C. Then stop 71 | the Docker container with `docker stop flash-shell` 72 | 73 | 12. Install PM2 with the command: 74 | 75 | `sudo npm install -g pm2` 76 | 77 | 13. Enable it as a startup service: 78 | 79 | `sudo env PATH=$PATH:/usr/local/bin pm2 startup systemd -u pi --hp /home/pi` 80 | 81 | 14. Start the P2P VPS client with this command: 82 | 83 | `pm2 start p2p-vps-client.js` 84 | 85 | and save the state of PM2 with this command: 86 | 87 | `pm2 save` 88 | -------------------------------------------------------------------------------- /client/rpi/flash-storage/device-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "deviceId": "5a4aa06045471e0028889993", 3 | "loggingPath": "/home/pi/.p2pvps/logs/p2p-vps-client.log", 4 | "serverIp": "p2pvps.net", 5 | "serverPort": 3001, 6 | "sshServer": "p2pvps.net", 7 | "sshServerPort": 6100 8 | } 9 | -------------------------------------------------------------------------------- /client/rpi/flash-storage/lib/buildImage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cp ../../lib/connect-client.js ./output-files/ 3 | cd output-files 4 | docker build -t flash-shell . 5 | -------------------------------------------------------------------------------- /client/rpi/flash-storage/lib/cleanupImages: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Remove all untagged docker images. 4 | docker rmi $(docker images | grep "^" | awk '{print $3}') 5 | -------------------------------------------------------------------------------- /client/rpi/flash-storage/lib/enterImage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #docker container run --name child-shell --rm -p 3100:3100 -v /media/cryptfs:/home/6SQw18oY8N/encrypted -it child-shell /bin/bash 3 | docker container run --name flash-shell --rm -p 6100:6100 -p 3100:3100 -v /media/usb:/media/storage -v /home/pi/.p2pvps/logs/connect-client:/usr/src/app/logs -it flash-shell /bin/bash 4 | -------------------------------------------------------------------------------- /client/rpi/flash-storage/lib/prep-flash-storage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This script formats and mounts a USB flash drive. 3 | 4 | echo Preparing flash drive... 5 | sudo umount /dev/sda1 6 | sudo mkfs.ext4 -F /dev/sda1 7 | sudo mkdir /media/usb 8 | sudo mount -t ext4 /dev/sda1 /media/usb/ 9 | sudo chown -R pi:pi /media/usb/ 10 | echo ...finished preparing flash drive. 11 | -------------------------------------------------------------------------------- /client/rpi/flash-storage/lib/runImage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Bandwidth limit the docker container 4 | sudo wondershaper docker0 256 128 5 | 6 | #Run the docker image 7 | #docker run -d --rm --name flash-shell -p 6100:6100 -p 3100:3100 -v /media/usb:/media/storage --restart unless-stopped flash-shell 8 | docker run -d --rm --name flash-shell -p 6100:6100 -p 3100:3100 -v /media/usb:/media/storage -v /home/pi/.p2pvps/logs/connect-client:/usr/src/app/logs flash-shell 9 | -------------------------------------------------------------------------------- /client/rpi/flash-storage/lib/stopImage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker stop flash-shell 3 | -------------------------------------------------------------------------------- /client/rpi/flash-storage/lib/write-files.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Chris Troutner & P2PVPS.org 3 | * Licensing Information: MIT License 4 | * 5 | * This program writes out the Dockerfile and various configuration files. 6 | */ 7 | 8 | "use strict"; 9 | 10 | const fs = require("fs"); 11 | 12 | class WriteFiles { 13 | constructor(deviceConfig) { 14 | this.port = ""; 15 | this.username = ""; 16 | this.password = ""; 17 | this.deviceId = deviceConfig.deviceId; 18 | this.serverIp = deviceConfig.serverIp; 19 | this.serverPort = deviceConfig.serverPort; 20 | this.sshServer = deviceConfig.sshServer; 21 | this.sshServerPort = deviceConfig.sshServerPort; 22 | } 23 | 24 | // This function writes out the Dockerfile. 25 | writeDockerfile(port, username, password) { 26 | this.port = port; 27 | this.username = username; 28 | this.password = password; 29 | 30 | return new Promise((resolve, reject) => { 31 | const fileString = `FROM resin/rpi-raspbian 32 | MAINTAINER Chris Troutner 33 | RUN apt-get -y update 34 | RUN apt-get install -y openssh-server 35 | RUN apt-get install -y nano 36 | RUN apt-get install -y ssh 37 | RUN mkdir /var/run/sshd 38 | RUN sed 's@sessions*requireds*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd 39 | ENV NOTVISIBLE "in users profile" 40 | RUN echo "export VISIBLE=now" >> /etc/profile 41 | RUN curl -sL https://deb.nodesource.com/setup_8.x -o nodesource_setup.sh 42 | RUN bash nodesource_setup.sh 43 | RUN apt-get install -y nodejs 44 | RUN apt-get install -y build-essential 45 | RUN apt-get install -y inetutils-ping 46 | RUN apt-get install -y git 47 | WORKDIR /root 48 | VOLUME /usr/src/app/logs 49 | COPY package.json package.json 50 | RUN npm install 51 | EXPOSE 3100 52 | COPY dummyapp.js dummyapp.js 53 | COPY finalsetup finalsetup 54 | COPY connect-client.js connect-client.js 55 | COPY package.json package.json 56 | COPY config.json config.json 57 | RUN chmod 775 finalsetup 58 | VOLUME /media/storage 59 | RUN useradd -ms /bin/bash ${username} 60 | RUN chown -R ${username} /media/storage 61 | RUN adduser ${username} sudo 62 | RUN echo ${username}:${password} | chpasswd 63 | EXPOSE ${port} 64 | #ENTRYPOINT ["./finalsetup", "node", "dummyapp.js"] 65 | ENTRYPOINT ["./finalsetup", "node", "connect-client.js"] 66 | `; 67 | 68 | fs.writeFile("./output-files/Dockerfile", fileString, function(err) { 69 | if (err) { 70 | debugger; 71 | console.error("Error while trying to write file: ", err); 72 | reject(err); 73 | } else { 74 | console.log("Dockerfile written successfully!"); 75 | resolve(); 76 | } 77 | }); 78 | }); 79 | } 80 | 81 | // writeClientConfig writes out the config.json file. 82 | writeClientConfig() { 83 | //debugger; 84 | 85 | return new Promise((resolve, reject) => { 86 | const fileJSON = { 87 | deviceId: this.deviceId, 88 | serverIp: this.serverIp, 89 | serverPort: this.serverPort, 90 | sshServer: this.sshServer, 91 | sshServerPort: this.sshServerPort, 92 | sshTunnelPort: this.port, 93 | }; 94 | 95 | fs.writeFile("./output-files/config.json", JSON.stringify(fileJSON, null, 2), function(err) { 96 | if (err) { 97 | debugger; 98 | console.error("Error while trying to write config.json file: ", err); 99 | reject(err); 100 | } else { 101 | console.log("config.json written successfully!"); 102 | resolve(); 103 | } 104 | }); 105 | }); 106 | } 107 | } 108 | 109 | module.exports = WriteFiles; 110 | -------------------------------------------------------------------------------- /client/rpi/flash-storage/output-files/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM resin/rpi-raspbian 2 | MAINTAINER Chris Troutner 3 | RUN apt-get -y update 4 | RUN apt-get install -y openssh-server 5 | RUN apt-get install -y nano 6 | RUN apt-get install -y ssh 7 | RUN mkdir /var/run/sshd 8 | RUN sed 's@sessions*requireds*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd 9 | ENV NOTVISIBLE "in users profile" 10 | RUN echo "export VISIBLE=now" >> /etc/profile 11 | RUN curl -sL https://deb.nodesource.com/setup_8.x -o nodesource_setup.sh 12 | RUN bash nodesource_setup.sh 13 | RUN apt-get install -y nodejs 14 | RUN apt-get install -y build-essential 15 | WORKDIR /root 16 | COPY package.json package.json 17 | RUN npm install 18 | EXPOSE 3100 19 | COPY dummyapp.js dummyapp.js 20 | COPY finalsetup finalsetup 21 | COPY connect-client.js connect-client.js 22 | COPY package.json package.json 23 | COPY config.json config.json 24 | RUN chmod 775 finalsetup 25 | VOLUME /media/storage 26 | RUN useradd -ms /bin/bash 6ggtePdLuV 27 | RUN chown -R 6ggtePdLuV /media/storage 28 | RUN adduser 6ggtePdLuV sudo 29 | RUN echo 6ggtePdLuV:gvNSpLq4Di | chpasswd 30 | EXPOSE 6062 31 | #ENTRYPOINT ["./finalsetup", "node", "dummyapp.js"] 32 | ENTRYPOINT ["./finalsetup", "node", "connect-client.js"] 33 | -------------------------------------------------------------------------------- /client/rpi/flash-storage/output-files/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "deviceId": "5a33303e2cfd5b00801546c9", 3 | "serverIp": "p2pvps.net", 4 | "serverPort": 3001, 5 | "sshServer": "p2pvps.net", 6 | "sshServerPort": 6100, 7 | "sshTunnelPort": "6062" 8 | } -------------------------------------------------------------------------------- /client/rpi/flash-storage/output-files/dummyapp.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | 3 | var app = express(); 4 | var port = 3125; 5 | 6 | /* 7 | * Config for Production and Development 8 | */ 9 | /* 10 | app.engine('handlebars', exphbs({ 11 | // Default Layout and locate layouts and partials 12 | defaultLayout: 'main', 13 | layoutsDir: 'views/layouts/', 14 | partialsDir: 'views/partials/' 15 | })); 16 | */ 17 | 18 | // Locate the views 19 | //app.set('views', __dirname + '/views'); 20 | 21 | // Locate the assets 22 | //app.use(express.static(__dirname + '/assets')); 23 | 24 | 25 | // Set Handlebars 26 | app.set('view engine', 'handlebars'); 27 | 28 | 29 | /* Start up the Express web server */ 30 | app.listen(process.env.PORT || port); 31 | console.log('Express started on port ' + port); 32 | 33 | -------------------------------------------------------------------------------- /client/rpi/flash-storage/output-files/finalsetup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Copyright 2017 RPiOVN.org 4 | #Licensing Information: http://rpiovn.org/license 5 | 6 | 7 | #This script should be edited to execute any merge scripts needed to 8 | #merge plugins and theme files before starting ConnextCMS/KeystoneJS. 9 | 10 | echo Running finalsetup script 11 | 12 | #Run any merge scripts to install plugins or site templates. 13 | #public/merge-site-plugin-files 14 | 15 | #cd ~/myCMS 16 | 17 | #Copy the /public directory for nginx 18 | #cp -r public/* ~/public 19 | 20 | #["/usr/sbin/sshd", "-D", "-p", "4100"] 21 | /usr/sbin/sshd -D -p 3100 & 22 | 23 | #Launch KeystoneJS and ConnextCMS 24 | exec "$@" 25 | -------------------------------------------------------------------------------- /client/rpi/flash-storage/output-files/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "connect-client.js", 3 | "version": "1.0.0", 4 | "description": "Connects the Client to the P2P VPS server", 5 | "main": "connectClient.js", 6 | "dependencies": { 7 | "execa": "^0.8.0", 8 | "express": "^4.16.2", 9 | "express3-handlebars": "^0.5.2", 10 | "node-promise": "^0.5.12", 11 | "request": "^2.83.0", 12 | "reverse-tunnel-ssh": "^1.1.0", 13 | "winston": "^2.4.0" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/RPiOVN/p2pvps-client" 18 | }, 19 | "devDependencies": {}, 20 | "scripts": { 21 | "test": "echo \"Error: no test specified\" && exit 1" 22 | }, 23 | "author": "chris.troutner@gmail.com", 24 | "license": "MIT" 25 | } 26 | -------------------------------------------------------------------------------- /client/rpi/flash-storage/p2p-vps-client.js: -------------------------------------------------------------------------------- 1 | /* 2 | This is the primary 'governor' application that drives a Client devices and allows it to communicate 3 | with a P2P VPS Server. The scope of this application is to: 4 | 5 | * It reads the deviceConfig.json file and registers the Client device with the P2P VPS server. 6 | 7 | * It builds the Docker container with information returned by the server after registration. 8 | 9 | * It launches the Docker container after being built. 10 | 11 | * It sends a heartbeat signal to the P2P VPS server every 10 minutes. The server responds with an 12 | expiration date. 13 | * (Maybe I can also send benchmark data to the server?) 14 | 15 | * When the expiration date is reached, or the Server can not be reached after 30 minutes, the governor 16 | software stops the Docker container and wipes the flash drive. It then reregisters itself with the 17 | P2P VPS marketplace. 18 | 19 | Specifications for this program can be found here: 20 | https://github.com/RPiOVN/p2pvps-server/blob/b1fd8e709f264db4a1d869e8939033ca39a895da/specifications/client-specification.md 21 | */ 22 | 23 | /* 24 | Copyright 2017 Chris Troutner & P2PVPS.org 25 | MIT License. See LICENSE.md for details. 26 | */ 27 | 28 | //This file registers with the server 29 | "use strict"; 30 | 31 | // Express Dependencies 32 | const express = require("express"); 33 | const execa = require("execa"); 34 | 35 | // Global Variables 36 | const app = express(); 37 | const port = 4000; 38 | 39 | // Read in device-config.json file 40 | let deviceConfig; 41 | try { 42 | deviceConfig = require("./device-config.json"); 43 | console.log(`Registering device ID ${deviceConfig.deviceId}`); 44 | } catch (err) { 45 | const msg = "Could not open the device-config.json file! Exiting."; 46 | console.error(msg, err); 47 | process.exit(1); 48 | } 49 | 50 | // Each type of client shell will have a unique write-files.js library. 51 | const WriteFiles = require("./lib/write-files.js"); 52 | const writeFiles = new WriteFiles(deviceConfig); 53 | 54 | // Initialize the debugging logger. 55 | const Logger = require("../../lib/logger.js"); 56 | const logr = new Logger(deviceConfig); 57 | 58 | // Utility functions for dealing with the P2P VPS server. Shared by all clients. 59 | const P2pVpsServer = require("../../lib/p2p-vps-server.js"); 60 | const p2pVpsServer = new P2pVpsServer(deviceConfig, logr); 61 | 62 | // Create an Express server. Future development will allow serving of webpages and creation of Client API. 63 | const ExpressServer = require("../../lib/express-server.js"); 64 | const expressServer = new ExpressServer(app, port); 65 | expressServer.start(); 66 | 67 | // This is a high-level function used to register the client with this Client with the Server. 68 | // It calls the registration function, writes out the support files, builds the Docker container, 69 | // and launches the Docker container. 70 | function registerDevice() { 71 | //Simulate benchmark tests with dummy data. 72 | const now = new Date(); 73 | const deviceSpecs = { 74 | memory: "Fake Test Data", 75 | diskSpace: "Fake Test Data", 76 | processor: "Fake Test Data", 77 | internetSpeed: "Fake Test Data", 78 | checkinTimeStamp: now.toISOString(), 79 | }; 80 | 81 | const config = { 82 | deviceId: deviceConfig.deviceId, 83 | deviceSpecs: deviceSpecs, 84 | }; 85 | 86 | const execaOptions = { 87 | stdout: "inherit", // Pipe output to stdout in real time. 88 | stderr: "inherit", // Pipe output to stderr in real time. 89 | }; 90 | 91 | // Register with the server. 92 | p2pVpsServer 93 | .register(config) 94 | 95 | // Write out support files (Dockerfile, reverse-tunnel.js) 96 | .then(clientData => { 97 | //debugger; 98 | 99 | // Save data to a global variable for use in later functions. 100 | global.clientData = clientData; 101 | 102 | return ( 103 | // Write out the Dockerfile. 104 | writeFiles 105 | .writeDockerfile( 106 | clientData.device.port, 107 | clientData.device.username, 108 | clientData.device.password 109 | ) 110 | 111 | // Write out config.json file. 112 | .then(() => writeFiles.writeClientConfig(clientData.port, deviceConfig.deviceId)) 113 | 114 | .catch(err => { 115 | logr.error("Problem writing out support files: ", err); 116 | }) 117 | ); 118 | }) 119 | 120 | // Wipe and mount the flash drive 121 | .then(() => { 122 | logr.log("Wiping and mounting flash drive."); 123 | 124 | return execa("./lib/prep-flash-storage", undefined, execaOptions) 125 | .then(result => { 126 | debugger; 127 | logr.log(result.stdout); 128 | }) 129 | .catch(err => { 130 | debugger; 131 | logr.error("Error while trying to wipe and mount flash drive!"); 132 | logr.error(JSON.stringify(err, null, 2)); 133 | process.exit(1); 134 | }); 135 | }) 136 | 137 | // Build the Docker container. 138 | .then(() => { 139 | logr.log("Building Docker Image."); 140 | 141 | return execa("./lib/buildImage", undefined, execaOptions) 142 | .then(result => { 143 | debugger; 144 | console.log(result.stdout); 145 | }) 146 | .catch(err => { 147 | debugger; 148 | logr.error("Error while trying to build Docker image!"); 149 | logr.error(JSON.stringify(err, null, 2)); 150 | process.exit(1); 151 | }); 152 | }) 153 | 154 | // Run the Docker container 155 | .then(() => { 156 | logr.log("Running the Docker image."); 157 | 158 | return execa("./lib/runImage", undefined, execaOptions) 159 | .then(result => { 160 | debugger; 161 | console.log(result.stdout); 162 | }) 163 | .catch(err => { 164 | debugger; 165 | logr.error("Error while trying to run Docker image!"); 166 | logr.error(JSON.stringify(err, null, 2)); 167 | process.exit(1); 168 | }); 169 | }) 170 | 171 | .then(() => { 172 | logr.log("Docker image has been built and is running."); 173 | 174 | // Begin timer to check expiration. 175 | console.log(`p2p-vps-client.js/registerDevice: ${typeof registerDevice}`); 176 | p2pVpsServer.startExpirationTimer(registerDevice); 177 | }) 178 | 179 | .catch(err => { 180 | logr.error("Error in main program: ", err); 181 | process.exit(1); 182 | }); 183 | } 184 | registerDevice(); 185 | -------------------------------------------------------------------------------- /client/rpi/flash-storage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "connect-client.js", 3 | "version": "1.0.0", 4 | "description": "Connects the Client to the P2P VPS server", 5 | "main": "connectClient.js", 6 | "dependencies": { 7 | "execa": "^0.8.0", 8 | "express": "^4.16.2", 9 | "express3-handlebars": "^0.5.2", 10 | "get-stream": "^3.0.0", 11 | "node-promise": "^0.5.12", 12 | "request": "^2.83.0", 13 | "request-promise": "^4.2.2", 14 | "reverse-tunnel-ssh": "^1.1.0", 15 | "winston": "^2.4.0" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/RPiOVN/p2pvps-client" 20 | }, 21 | "devDependencies": {}, 22 | "scripts": { 23 | "test": "echo \"Error: no test specified\" && exit 1" 24 | }, 25 | "author": "chris.troutner@gmail.com", 26 | "license": "MIT" 27 | } 28 | -------------------------------------------------------------------------------- /client/rpi/simple/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock = false 2 | -------------------------------------------------------------------------------- /client/rpi/simple/README.md: -------------------------------------------------------------------------------- 1 | # Simple VPS Client 2 | This is the simplest possible implementation of a Virtual Private Server (VPS). It spins up 3 | a Raspbian-based Docker container with SSH shell access. There is no persistent storage, 4 | so everything is held in memory and deleted if the device is rebooted. The SSH user also 5 | does not have sudo privileges so is pretty strongly restricted in what they can do. 6 | 7 | However, by keeping it simple, this client is preferred for testing. If you're a beginner at 8 | setting up a P2P VPS client, you should start by following the directions below. It's also 9 | much easier to troubleshoot if you build this client **before** building the 10 | [flash-storage client](../flash-storage). 11 | 12 | ## Installation 13 | These instructions assume you are starting with a Raspberry Pi v3 B+ with an 8GB or larger 14 | SD card. It also assumes that you are starting with a fresh install of Raspbian OS, which can be 15 | [installed via NOOBS](https://www.raspberrypi.org/documentation/installation/noobs.md). 16 | 17 | It will take an hour or two for the Raspberry Pi to execute all the instructions in this 18 | document. If you are provisioning several Pi's for use on the P2P VPS network, it's much 19 | faster to copy the SD card image after completing setup on the first device. The built-in 20 | [SD card copier](https://www.raspberrypi.org/blog/another-update-raspbian/) feature in 21 | Raspbian is the easiest way to do this. Unfortunately, there is no command-line version 22 | of that application. 23 | 24 | ### Device Configuration 25 | 26 | 1. After installing Raspbian, you need to 27 | [enable SSH](https://www.raspberrypi.org/documentation/remote-access/ssh/) and ensure you can 28 | connect to your RPi over your local area network using SSH. I prefer to use 29 | [Putty](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html) as my SSH terminal on 30 | Windows. It's light weight and powerful. 31 | 32 | 2. The first thing to do after logging into the Raspberry Pi is change the default password. 33 | From the SSH command line terminal, issue the command `passwd` and change the default password 34 | for the user `pi`. 35 | 36 | 3. (optional) A fresh install of Raspbian on an 8GB card does not leave much room. 37 | If you plan to designate this device as a dedicated VPS, it will be advantageous 38 | to remove a lot of unneeded software. 39 | Follow the commands below to remove a lot of the default applications in Raspbian. 40 | 41 | ``` 42 | sudo apt-get --purge -y remove libreoffice libreoffice-avmedia-backend-gstreamer libreoffice-base libreoffice-base-core libreoffice-base-drivers libreoffice-calc libreoffice-common libreoffice-core libreoffice-draw libreoffice-gtk libreoffice-impress libreoffice-java-common libreoffice-math libreoffice-report-builder-bin libreoffice-sdbc-hsqldb libreoffice-style-galaxy libreoffice-writer bluej wolfram-engine scratch geany geany-common greenfoot sonic-pi 43 | 44 | sudo apt-get --purge -y remove minecraft-pi chromium-browser 45 | 46 | sudo apt-get -y autoremove 47 | ``` 48 | 49 | 4. This is a great time to update the software on the device, including any security patches. 50 | ``` 51 | sudo apt-get -y update 52 | 53 | sudo apt-get -y remove nodejs 54 | curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - 55 | sudo apt-get install -y nodejs build-essential 56 | 57 | sudo apt-get -y upgrade 58 | ``` 59 | 60 | 5. (optional) For renting out as a VPS, I usually hard connect the RPi with an 61 | ethernet cord. That makes it possible to disable to the WiFi and Bluetooth to 62 | reduce power, save money, and prevent an attack vector. It's easy to disable by 63 | adding these two lines to the bottom of `/boot/config.txt`: 64 | ``` 65 | dtoverlay=pi3-disable-wifi 66 | pi3-disable-bt 67 | ``` 68 | 69 | 6. You'll also need to install Docker on the RPi. Prior to running the instructions below, 70 | this is a great time to reboot your device. It seems to prevent errors with installing Docker. 71 | 72 | `curl -sSL https://get.docker.com | sh` 73 | 74 | 7. Follow the on-screen instructions to add the user 'pi' to the docker group. 75 | You'll need to open a new terminal, or reboot your device, after entering this 76 | instruction: 77 | 78 | `sudo usermod -aG docker pi` 79 | 80 | 8. Now, downgrade Docker (until they fix issues with the newer versions): 81 | 82 | `sudo apt install docker-ce=17.09.0~ce-0~raspbian` 83 | 84 | 9. (optional) create a directory for your node applications, like this one: 85 | 86 | `mkdir node` 87 | 88 | 10. Clone this repository: 89 | 90 | `git clone https://github.com/P2PVPS/p2pvps-client` 91 | 92 | 11. Setup the Client program by running: 93 | ``` 94 | cd p2pvps-client/client 95 | npm install 96 | ./init 97 | ``` 98 | 99 | 12. Change into the `rpi` directory and run the `init` script: 100 | ``` 101 | cd rpi 102 | ./init 103 | ``` 104 | 105 | 13. Change into the RPi `simple` client directory: 106 | 107 | `cd simple/` 108 | 109 | 14. Install the dependencies: 110 | 111 | `npm install` 112 | 113 | 15. Get your device GUID from [the P2P VPS marketplace](http://p2pvps.net). You'll need to create an account. The GUID is provided in 114 | the *Owned Devices view* by clicking the *+Add New Device* button. Paste this GUID into the `device-config.json` file. 115 | 116 | 16. Launch the simple client. The first time will take a while as it will need to download and 117 | build several Docker containers: 118 | 119 | `node p2p-vps-client.js` 120 | 121 | That's it! Once the application presents the message 122 | `Docker image has been built and is running.`, 123 | your device is now connected to the P2P VPS server and listed on the market for rent. 124 | 125 | Continue by installing the [flash client](../flash-storage/README.md) 126 | -------------------------------------------------------------------------------- /client/rpi/simple/device-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "deviceId": "5a4aa06045471e0028889993", 3 | "loggingPath": "/home/pi/.p2pvps/logs/p2p-vps-client.log", 4 | "serverIp": "p2pvps.net", 5 | "serverPort": 3001, 6 | "sshServer": "p2pvps.net", 7 | "sshServerPort": 6100 8 | } 9 | -------------------------------------------------------------------------------- /client/rpi/simple/lib/buildImage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cp ../../lib/connect-client.js ./output-files/ 3 | cd output-files/ 4 | docker build -t renter-shell . 5 | -------------------------------------------------------------------------------- /client/rpi/simple/lib/cleanupImages: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Remove all untagged docker images. 4 | docker rmi $(docker images | grep "^" | awk '{print $3}') 5 | -------------------------------------------------------------------------------- /client/rpi/simple/lib/enterImage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker container run --name renter-shell --rm -p 3100:3100 -p 6100:6100 -it renter-shell /bin/bash 3 | -------------------------------------------------------------------------------- /client/rpi/simple/lib/runImage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Copy the SSH config file to stop the irritating key prompt 4 | #cp config ~/.ssh/ 5 | 6 | #Run the docker image 7 | docker run -d --rm --name renter-shell -p 6100:6100 -p 3100:3100 -v /home/pi/.p2pvps/logs/connect-client/:/usr/src/app/logs renter-shell 8 | -------------------------------------------------------------------------------- /client/rpi/simple/lib/stopImage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker stop renter-shell 3 | -------------------------------------------------------------------------------- /client/rpi/simple/lib/write-files.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Chris Troutner & P2PVPS.org 3 | * Licensing Information: MIT License 4 | * 5 | * This program writes out the Dockerfile and various configuration files. 6 | */ 7 | 8 | "use strict"; 9 | 10 | const fs = require("fs"); 11 | 12 | class WriteFiles { 13 | constructor(deviceConfig) { 14 | this.port = ""; 15 | this.username = ""; 16 | this.password = ""; 17 | this.deviceId = deviceConfig.deviceId; 18 | this.serverIp = deviceConfig.serverIp; 19 | this.serverPort = deviceConfig.serverPort; 20 | this.sshServer = deviceConfig.sshServer; 21 | this.sshServerPort = deviceConfig.sshServerPort; 22 | } 23 | 24 | // This function writes out the Dockerfile. 25 | writeDockerfile(port, username, password) { 26 | this.port = port; 27 | this.username = username; 28 | this.password = password; 29 | 30 | return new Promise((resolve, reject) => { 31 | const fileString = `FROM resin/rpi-raspbian 32 | MAINTAINER Chris Troutner 33 | RUN apt-get -y update 34 | RUN apt-get install -y openssh-server 35 | RUN apt-get install -y nano 36 | RUN apt-get install -y ssh 37 | RUN mkdir /var/run/sshd 38 | RUN sed 's@sessions*requireds*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd 39 | ENV NOTVISIBLE "in users profile" 40 | RUN echo "export VISIBLE=now" >> /etc/profile 41 | RUN curl -sL https://deb.nodesource.com/setup_8.x -o nodesource_setup.sh 42 | RUN bash nodesource_setup.sh 43 | RUN apt-get install -y nodejs 44 | RUN apt-get install -y build-essential 45 | RUN apt-get install -y inetutils-ping 46 | WORKDIR /root 47 | VOLUME /usr/src/app/logs 48 | COPY package.json package.json 49 | RUN npm install 50 | EXPOSE 3100 51 | COPY dummyapp.js dummyapp.js 52 | COPY finalsetup finalsetup 53 | COPY connect-client.js connect-client.js 54 | COPY package.json package.json 55 | COPY config.json config.json 56 | RUN chmod 775 finalsetup 57 | RUN useradd -ms /bin/bash ${username} 58 | RUN adduser ${username} sudo 59 | RUN echo ${username}:${password} | chpasswd 60 | EXPOSE ${port} 61 | #ENTRYPOINT ["./finalsetup", "node", "dummyapp.js"] 62 | ENTRYPOINT ["./finalsetup", "node", "connect-client.js"] 63 | `; 64 | 65 | fs.writeFile("./output-files/Dockerfile", fileString, function(err) { 66 | if (err) { 67 | debugger; 68 | console.error("Error while trying to write file: ", err); 69 | reject(err); 70 | } else { 71 | console.log("Dockerfile written successfully!"); 72 | resolve(); 73 | } 74 | }); 75 | }); 76 | } 77 | 78 | // writeClientConfig writes out the config.json file. 79 | writeClientConfig() { 80 | //debugger; 81 | 82 | return new Promise((resolve, reject) => { 83 | const fileJSON = { 84 | deviceId: this.deviceId, 85 | serverIp: this.serverIp, 86 | serverPort: this.serverPort, 87 | sshServer: this.sshServer, 88 | sshServerPort: this.sshServerPort, 89 | sshTunnelPort: this.port, 90 | }; 91 | 92 | fs.writeFile("./output-files/config.json", JSON.stringify(fileJSON, null, 2), function(err) { 93 | if (err) { 94 | debugger; 95 | console.error("Error while trying to write config.json file: ", err); 96 | reject(err); 97 | } else { 98 | console.log("config.json written successfully!"); 99 | resolve(); 100 | } 101 | }); 102 | }); 103 | } 104 | } 105 | 106 | module.exports = WriteFiles; 107 | -------------------------------------------------------------------------------- /client/rpi/simple/output-files/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM resin/rpi-raspbian 2 | MAINTAINER Chris Troutner 3 | RUN apt-get -y update 4 | RUN apt-get install -y openssh-server 5 | RUN apt-get install -y nano 6 | RUN apt-get install -y ssh 7 | RUN mkdir /var/run/sshd 8 | RUN sed 's@sessions*requireds*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd 9 | ENV NOTVISIBLE "in users profile" 10 | RUN echo "export VISIBLE=now" >> /etc/profile 11 | RUN curl -sL https://deb.nodesource.com/setup_8.x -o nodesource_setup.sh 12 | RUN bash nodesource_setup.sh 13 | RUN apt-get install -y nodejs 14 | RUN apt-get install -y build-essential 15 | WORKDIR /root 16 | VOLUME /usr/src/app/logs 17 | COPY package.json package.json 18 | RUN npm install 19 | EXPOSE 3100 20 | COPY dummyapp.js dummyapp.js 21 | COPY finalsetup finalsetup 22 | COPY connect-client.js connect-client.js 23 | COPY package.json package.json 24 | COPY config.json config.json 25 | RUN chmod 775 finalsetup 26 | RUN useradd -ms /bin/bash nLhOKGNFIt 27 | RUN echo nLhOKGNFIt:YOhKM21Kaj | chpasswd 28 | EXPOSE 6127 29 | #ENTRYPOINT ["./finalsetup", "node", "dummyapp.js"] 30 | ENTRYPOINT ["./finalsetup", "node", "connect-client.js"] 31 | -------------------------------------------------------------------------------- /client/rpi/simple/output-files/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "deviceId": "5a4aa06045471e0028889993", 3 | "serverIp": "p2pvps.net", 4 | "serverPort": 3001, 5 | "sshServer": "p2pvps.net", 6 | "sshServerPort": 6100, 7 | "sshTunnelPort": "6127" 8 | } -------------------------------------------------------------------------------- /client/rpi/simple/output-files/connect-client.js: -------------------------------------------------------------------------------- 1 | /* 2 | * The primary functions of this program are to: 3 | * -Establish a reverse SSH tunnel with the SSH server 4 | * -Run a heartbeat API that updates a timestamp on the P2P VPS server every 2 minutes. 5 | */ 6 | 7 | "use strict"; 8 | 9 | const tunnel = require("reverse-tunnel-ssh"); //tunnel is a ssh2 clientConnection object 10 | const request = require("request"); //Used for CURL style requests. 11 | const express = require("express"); 12 | const winston = require("winston"); 13 | 14 | global.config = false; 15 | 16 | try { 17 | global.config = require("./config.json"); 18 | winston.info(`Connecting device to P2P VPS server with ID ${global.config.deviceId}`); 19 | } catch (err) { 20 | console.error("Could not open the config.json file!", err); 21 | process.exit(1); 22 | } 23 | 24 | // Set up the Winston logging. 25 | winston.add(winston.transports.File, { 26 | filename: "/usr/src/app/logs/connect-client.log", 27 | maxFiles: 1, 28 | colorize: false, 29 | timestamp: true, 30 | datePattern: ".yyyy-MM-ddTHH-mm", 31 | maxsize: 1000000, 32 | json: false, 33 | }); 34 | 35 | // Set the logging level. 36 | winston.level = "debug"; 37 | 38 | // Start first line of the log. 39 | const now = new Date(); 40 | winston.log("info", `Application starting at ${now}`); 41 | 42 | const app = express(); 43 | const port = 4010; 44 | 45 | /* 46 | * Use Handlebars for templating 47 | */ 48 | const exphbs = require("express3-handlebars"); 49 | //let hbs; 50 | 51 | // For gzip compression 52 | //app.use(express.compress()); 53 | 54 | /* 55 | * Config for Production and Development 56 | */ 57 | app.engine( 58 | "handlebars", 59 | exphbs({ 60 | // Default Layout and locate layouts and partials 61 | defaultLayout: "main", 62 | layoutsDir: "views/layouts/", 63 | partialsDir: "views/partials/", 64 | }) 65 | ); 66 | 67 | // Locate the views 68 | app.set("views", `${__dirname}/views`); 69 | 70 | // Locate the assets 71 | app.use(express.static(`${__dirname}/assets`)); 72 | 73 | // Set Handlebars 74 | app.set("view engine", "handlebars"); 75 | 76 | // Index Page 77 | app.get("/", function(request, response, next) { 78 | response.render("index"); 79 | }); 80 | 81 | /* Start up the Express web server */ 82 | app.listen(process.env.PORT || port); 83 | winston.info(`P2P VPS Keep Alive timer started on port ${port}`); 84 | 85 | // Check in with the P2P VPS server every 2 minutes with this API. 86 | // This lets the server know the Client is still connected. 87 | const checkInTimer = setInterval(function() { 88 | //Register with the server by sending the benchmark data. 89 | request.get( 90 | { 91 | url: `http://${global.config.serverIp}:${global.config.serverPort}/api/deviceCheckIn/${ 92 | global.config.deviceId 93 | }`, 94 | // form: obj 95 | }, 96 | function(error, response, body) { 97 | try { 98 | debugger; 99 | //If the request was successfull, the server will respond with username, password, and port to be 100 | //used to build the Docker file. 101 | if (!error && response.statusCode === 200) { 102 | debugger; 103 | 104 | //Convert the data from a string into a JSON object. 105 | const data = JSON.parse(body); //Convert the returned JSON to a JSON string. 106 | 107 | if (data.success) { 108 | const now = new Date(); 109 | winston.info( 110 | `Checked in for device ${global.config.deviceId} at ${now.toLocaleString()}` 111 | ); 112 | } else { 113 | console.error(`Check-in failed for ${global.config.deviceId}`); 114 | } 115 | } else { 116 | debugger; 117 | 118 | // Server responded with some other status than 200. 119 | if (response) { 120 | if (response.statusCode !== 200) 121 | console.error("P2P VPS server rejected checking: ", response); 122 | } else if (error.code === "EHOSTUNREACH" || error.code === "ECONNREFUSED") { 123 | // Could not connect to the server. 124 | debugger; 125 | winston.info(`Warning: Could not connect to server at ${now.toLocaleString()}`); 126 | return; 127 | } else { 128 | console.error( 129 | "Server responded with error when trying to register the device: ", 130 | error 131 | ); 132 | console.error( 133 | "Ensure the ID in your deviceGUID.json file matches the ID in the Owned Devices section of the marketplace." 134 | ); 135 | } 136 | } 137 | } catch (err) { 138 | winston.info(`connect-client.js exiting with error: ${err}`); 139 | } 140 | } 141 | ); 142 | }, 120000); 143 | 144 | // Establish a reverse SSH connection. 145 | function createTunnel() { 146 | try { 147 | const conn = tunnel( 148 | { 149 | host: global.config.sshServer, 150 | port: global.config.sshServerPort, //The SSH port on the server. 151 | username: "sshuser", 152 | password: "sshuserpassword", 153 | dstHost: "0.0.0.0", // bind to all IPv4 interfaces 154 | dstPort: global.config.sshTunnelPort, //The new port that will be opened 155 | //srcHost: '127.0.0.1', // default 156 | srcPort: 3100, // The port on the Pi to tunnel to. 157 | //readyTimeout: 20000, 158 | //debug: myDebug, 159 | }, 160 | function(error, clientConnection) { 161 | if (error) { 162 | winston.info("There was an error in connect-client.js/tunnel()!"); 163 | console.error(JSON.stringify(error, null, 2)); 164 | } else { 165 | winston.info( 166 | `Reverse tunnel established on destination port ${global.config.sshTunnelPort}` 167 | ); 168 | } 169 | } 170 | ); 171 | 172 | conn.on("error", function(error) { 173 | debugger; 174 | 175 | // Could not connect to the internet. 176 | if (error.level === "client-timeout" || error.level === "client-socket") { 177 | debugger; 178 | winston.info("Warning, could not connect to SSH server. Waiting before retry."); 179 | } else { 180 | console.error("Error with connect-client.js: "); 181 | console.error(JSON.stringify(error, null, 2)); 182 | } 183 | 184 | // Try again in a short while. 185 | setTimeout(function() { 186 | createTunnel(); 187 | }, 30000); 188 | }); 189 | 190 | conn.on("close", function(val) { 191 | winston.info("SSH connection for connect-client.js was closed."); 192 | }); 193 | 194 | conn.on("end", function(val) { 195 | winston.info("SSH connection for connect-client ended."); 196 | }); 197 | } catch (err) { 198 | console.error("I caught the error!"); 199 | console.error(JSON.stringify(err, null, 2)); 200 | } 201 | } 202 | 203 | // Execute the first time. 204 | setTimeout(function() { 205 | createTunnel(); 206 | }, 5000); 207 | 208 | function myDebug(val) { 209 | winston.info(val); 210 | } 211 | -------------------------------------------------------------------------------- /client/rpi/simple/output-files/dummyapp.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | 3 | var app = express(); 4 | var port = 3125; 5 | 6 | /* 7 | * Config for Production and Development 8 | */ 9 | /* 10 | app.engine('handlebars', exphbs({ 11 | // Default Layout and locate layouts and partials 12 | defaultLayout: 'main', 13 | layoutsDir: 'views/layouts/', 14 | partialsDir: 'views/partials/' 15 | })); 16 | */ 17 | 18 | // Locate the views 19 | //app.set('views', __dirname + '/views'); 20 | 21 | // Locate the assets 22 | //app.use(express.static(__dirname + '/assets')); 23 | 24 | 25 | // Set Handlebars 26 | app.set('view engine', 'handlebars'); 27 | 28 | 29 | /* Start up the Express web server */ 30 | app.listen(process.env.PORT || port); 31 | console.log('Express started on port ' + port); 32 | 33 | -------------------------------------------------------------------------------- /client/rpi/simple/output-files/finalsetup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Copyright 2017 RPiOVN.org 4 | #Licensing Information: http://rpiovn.org/license 5 | 6 | 7 | #This script should be edited to execute any merge scripts needed to 8 | #merge plugins and theme files before starting ConnextCMS/KeystoneJS. 9 | 10 | echo Running finalsetup script 11 | 12 | #Run any merge scripts to install plugins or site templates. 13 | #public/merge-site-plugin-files 14 | 15 | #cd ~/myCMS 16 | 17 | #Copy the /public directory for nginx 18 | #cp -r public/* ~/public 19 | 20 | #["/usr/sbin/sshd", "-D", "-p", "4100"] 21 | /usr/sbin/sshd -D -p 3100 & 22 | 23 | #Launch KeystoneJS and ConnextCMS 24 | exec "$@" 25 | -------------------------------------------------------------------------------- /client/rpi/simple/output-files/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "connect-client.js", 3 | "version": "1.0.1", 4 | "description": "Connects the Client to the P2P VPS server", 5 | "main": "connectClient.js", 6 | "dependencies": { 7 | "execa": "^0.8.0", 8 | "express": "^4.16.2", 9 | "express3-handlebars": "^0.5.2", 10 | "node-promise": "^0.5.12", 11 | "request": "^2.83.0", 12 | "reverse-tunnel-ssh": "^1.1.0", 13 | "winston": "^2.4.0" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/RPiOVN/p2pvps-client" 18 | }, 19 | "devDependencies": {}, 20 | "scripts": { 21 | "test": "echo \"Error: no test specified\" && exit 1" 22 | }, 23 | "author": "chris.troutner@gmail.com", 24 | "license": "MIT" 25 | } 26 | -------------------------------------------------------------------------------- /client/rpi/simple/p2p-vps-client.js: -------------------------------------------------------------------------------- 1 | /* 2 | This is the primary 'governor' application that drives a Client devices and allows it to communicate 3 | with a P2P VPS Server. The scope of this application is to: 4 | 5 | * It reads the device-config.json file and registers the Client device with the P2P VPS server. 6 | 7 | * It builds the Docker container with information returned by the server after registration. 8 | 9 | * It launches the Docker container after being built. 10 | 11 | * It sends a heartbeat signal to the P2P VPS server every 10 minutes. The server responds with an 12 | expiration date. 13 | * (Maybe I can also send benchmark data to the server?) 14 | 15 | * When the expiration date is reached, or the Server can not be reached after 30 minutes, the governor 16 | software stops the Docker container and wipes the flash drive. It then reregisters itself with the 17 | P2P VPS marketplace. 18 | 19 | Specifications for this program can be found here: 20 | https://github.com/RPiOVN/p2pvps-server/blob/b1fd8e709f264db4a1d869e8939033ca39a895da/specifications/client-specification.md 21 | */ 22 | 23 | /* 24 | * Copyright 2017 Chris Troutner & P2PVPS.org 25 | * MIT License. See LICENSE.md for details. 26 | */ 27 | 28 | //This file registers with the server 29 | "use strict"; 30 | 31 | // Dependencies 32 | const express = require("express"); 33 | const getStream = require("get-stream"); 34 | const execa = require("execa"); 35 | 36 | // Global Variables 37 | const app = express(); 38 | const port = 4000; 39 | let checkExpirationTimer; 40 | 41 | // Read in device-config.json file 42 | let deviceConfig; 43 | try { 44 | deviceConfig = require("./device-config.json"); 45 | console.log(`Registering device ID ${deviceConfig.deviceId}`); 46 | } catch (err) { 47 | const msg = "Could not open the device-config.json file! Exiting."; 48 | console.error(msg, err); 49 | process.exit(1); 50 | } 51 | 52 | // Each type of client shell will have a unique write-files.js library. 53 | const WriteFiles = require("./lib/write-files.js"); 54 | const writeFiles = new WriteFiles(deviceConfig); 55 | 56 | // Initialize the debugging logger. 57 | const Logger = require("../../lib/logger.js"); 58 | const logr = new Logger(deviceConfig); 59 | 60 | // Utility functions for dealing with the P2P VPS server. Shared by all clients. 61 | const P2pVpsServer = require("../../lib/p2p-vps-server.js"); 62 | const p2pVpsServer = new P2pVpsServer(deviceConfig, logr); 63 | 64 | // Create an Express server. Future development will allow serving of webpages and creation of Client API. 65 | const ExpressServer = require("../../lib/express-server.js"); 66 | const expressServer = new ExpressServer(app, port); 67 | expressServer.start(); 68 | 69 | // This is a high-level function used to register the client with this Client with the Server. 70 | // It calls the registration function, writes out the support files, builds the Docker container, 71 | // and launches the Docker container. 72 | function registerDevice() { 73 | //Simulate benchmark tests with dummy data. 74 | const now = new Date(); 75 | const deviceSpecs = { 76 | memory: "Fake Test Data", 77 | diskSpace: "Fake Test Data", 78 | processor: "Fake Test Data", 79 | internetSpeed: "Fake Test Data", 80 | checkinTimeStamp: now.toISOString(), 81 | }; 82 | 83 | const config = { 84 | deviceId: deviceConfig.deviceId, 85 | deviceSpecs: deviceSpecs, 86 | }; 87 | 88 | const execaOptions = { 89 | stdout: "inherit", 90 | stderr: "inherit", 91 | }; 92 | 93 | // Register with the server. 94 | p2pVpsServer 95 | .register(config) 96 | 97 | // Write out support files (Dockerfile, reverse-tunnel.js) 98 | .then(clientData => { 99 | //debugger; 100 | 101 | // Save data to a global variable for use in later functions. 102 | global.clientData = clientData.device; 103 | 104 | return ( 105 | writeFiles 106 | // Write out the Dockerfile. 107 | .writeDockerfile( 108 | clientData.device.port, 109 | clientData.device.username, 110 | clientData.device.password) 111 | 112 | .then(() => 113 | // Write out config.json file. 114 | writeFiles.writeClientConfig() 115 | ) 116 | 117 | .catch(err => { 118 | logr.error("Problem writing out support files: ", err); 119 | }) 120 | ); 121 | }) 122 | 123 | // Build the Docker container. 124 | .then(() => { 125 | logr.log("Building Docker Image."); 126 | 127 | return execa("./lib/buildImage", undefined, execaOptions) 128 | .then(result => { 129 | //debugger; 130 | console.log(result.stdout); 131 | }) 132 | .catch(err => { 133 | debugger; 134 | console.error("Error while trying to build Docker image!", err); 135 | logr.error("Error while trying to build Docker image!", err); 136 | logr.error(JSON.stringify(err, null, 2)); 137 | process.exit(1); 138 | }); 139 | }) 140 | 141 | // Run the Docker container 142 | .then(() => { 143 | logr.log("Running the Docker image."); 144 | 145 | return execa("./lib/runImage", undefined, execaOptions) 146 | .then(result => { 147 | //debugger; 148 | console.log(result.stdout); 149 | }) 150 | .catch(err => { 151 | debugger; 152 | logr.error("Error while trying to run Docker image!"); 153 | logr.error(JSON.stringify(err, null, 2)); 154 | process.exit(1); 155 | }); 156 | }) 157 | 158 | .then(() => { 159 | logr.log("Docker image has been built and is running."); 160 | 161 | // Begin timer to check expiration. 162 | p2pVpsServer.startExpirationTimer(registerDevice); 163 | }) 164 | 165 | .catch(err => { 166 | logr.error("Error in main program: ", err); 167 | process.exit(1); 168 | }); 169 | } 170 | registerDevice(); 171 | -------------------------------------------------------------------------------- /client/rpi/simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "connect-client.js", 3 | "version": "1.0.1", 4 | "description": "Connects the Client to the P2P VPS server", 5 | "main": "connectClient.js", 6 | "dependencies": { 7 | "execa": "^0.8.0", 8 | "express": "^4.16.2", 9 | "express3-handlebars": "^0.5.2", 10 | "get-stream": "^3.0.0", 11 | "node-promise": "^0.5.12", 12 | "request": "^2.85.0", 13 | "request-promise": "^4.2.2", 14 | "reverse-tunnel-ssh": "^1.1.0" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/RPiOVN/p2pvps-client" 19 | }, 20 | "devDependencies": {}, 21 | "scripts": { 22 | "test": "echo \"Error: no test specified\" && exit 1" 23 | }, 24 | "author": "chris.troutner@gmail.com", 25 | "license": "MIT" 26 | } 27 | -------------------------------------------------------------------------------- /client/vm/README.md: -------------------------------------------------------------------------------- 1 | This directory contains client code customized to run on a VirtualBox Ubuntu VM. 2 | -------------------------------------------------------------------------------- /client/vm/flash-storage/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock = false 2 | -------------------------------------------------------------------------------- /client/vm/flash-storage/README.md: -------------------------------------------------------------------------------- 1 | # VM Flash Storage VPS Client 2 | This version of the VM client evolved from the simple-shell. Unlike that shell, 3 | it provides persistent storage and sudo privileges. It's modeled after the 4 | flash-shell client developed for the Raspberry Pi. 5 | 6 | ## Installation 7 | To prepare you device for the P2P VPS marketplace 8 | with this shell, run the following commands. 9 | ()[Source](http://www.walkernews.net/2007/07/01/create-linux-loopback-file-system-on-disk-file/)) 10 | 11 | 1. Follow steps 1 through 9 for the [simple client](../simple), then enter the home 12 | directory: 13 | 14 | `cd` 15 | 16 | 2. Create a new mount point with this command: 17 | 18 | `sudo mkdir /media/storage` 19 | 20 | * Create a block device, which will become the persistent storage folder inside the 21 | Docker container. The command below creates a 64GB file with 1MB blocks. Adjust 22 | it for your needs: 23 | 24 | `dd if=/dev/zero of=vps-storage bs=1M count=64000` 25 | 26 | * Allow Linux to access the file as if it was a file system with this command: 27 | 28 | `sudo losetup /dev/loop0 vps-storage` 29 | 30 | * Format the new drive: 31 | 32 | `sudo mkfs.ext4 /dev/loop0` 33 | 34 | * Release the loopback device now that the file has been prepared: 35 | 36 | `sudo losetup -d /dev/loop0` 37 | 38 | 7. Enter the flash-storage directory: 39 | 40 | `cd node/p2pvps-client/client/vm/flash-storage` 41 | 42 | 8. Get your device GUID from the P2P VPS marketplace. This is provided in 43 | the *Owned Devices view* by clicking the *+Add New Device* button. Paste this GUID into the `device-config.json` file. 44 | 45 | * You will also need to add the sudo password for this VM to the `device-config.json` file. The `p2p-vps-client.js` program 46 | will need this password in order to format the persistant storage. 47 | 48 | 9. Launch the simple client. The first time will take a while as it will need to download and 49 | build several Docker containers: 50 | 51 | `sudo node p2p-vps-client.js` 52 | 53 | That's it! Once the application presents the message `Docker image has been built and is running.`, 54 | your device is now connected to the P2P VPS server and listed on the market for rent. 55 | 56 | 10. If you've successfully gotten this far, the next step is to get the software to 57 | start on bootup. First, stop the programs you just started by hitting Ctrl-C. Then stop 58 | the Docker container with `docker stop flash-shell` 59 | 60 | 11. Install PM2 with the command: 61 | 62 | `sudo npm install -g pm2` 63 | 64 | 12. Start the P2P VPS client with this command: 65 | 66 | `pm2 start p2p-vps-client.js` 67 | 68 | 13. Finally, save the process with this command: 69 | 70 | `sudo env PATH=$PATH:/usr/local/bin pm2 startup systemd -u pi --hp /home/pi` 71 | 72 | and save the state of PM2 with this command: 73 | 74 | `npm save` 75 | -------------------------------------------------------------------------------- /client/vm/flash-storage/device-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "deviceId": "5a4567a2236fa30097d94420", 3 | "loggingPath": "/home/trout/.p2pvps/logs/p2p-vps-client.log", 4 | "sudoPassword": "sudoPassword", 5 | "serverIp": "p2pvps.net", 6 | "serverPort": 3001, 7 | "sshServer": "p2pvps.net", 8 | "sshServerPort": 6100 9 | } 10 | -------------------------------------------------------------------------------- /client/vm/flash-storage/lib/buildImage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cp ../../lib/connect-client.js ./output-files/ 3 | cd output-files 4 | docker build -t renter-shell . 5 | -------------------------------------------------------------------------------- /client/vm/flash-storage/lib/cleanupImages: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Remove all untagged docker images. 4 | docker rmi $(docker images | grep "^" | awk '{print $3}') 5 | -------------------------------------------------------------------------------- /client/vm/flash-storage/lib/enterImage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker container run --name renter-shell --rm -p 3100:3100 -p 6100:6100 -it renter-shell /bin/bash 3 | -------------------------------------------------------------------------------- /client/vm/flash-storage/lib/prep-flash-storage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This script formats and mounts a USB flash drive. 3 | 4 | echo Preparing persistent storage... 5 | 6 | if grep -qs '/media/storage' /proc/mounts; then 7 | echo "Storage drive is mounted." 8 | else 9 | echo "Storage drive NOT mounted." 10 | fi 11 | 12 | echo "User is $USER with id $UID" 13 | id 14 | losetup /dev/loop0 ~/vps-storage 15 | umount /dev/loop0 16 | mkfs.ext4 -F /dev/loop0 17 | mkdir /media/storage 18 | mount -t ext4 /dev/loop0 /media/storage 19 | 20 | if grep -qs '/media/storage' /proc/mounts; then 21 | echo "Storage drive is mounted." 22 | sudo rm -rf /media/storage/* 23 | mkdir /media/storage/lost+found 24 | else 25 | echo "Storage drive NOT mounted." 26 | fi 27 | 28 | echo ...finished preparing persistent storage. 29 | -------------------------------------------------------------------------------- /client/vm/flash-storage/lib/runImage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Copy the SSH config file to stop the irritating key prompt 4 | #cp config ~/.ssh/ 5 | 6 | #Run the docker image 7 | docker run -d --rm --name renter-shell -p 6100:6100 -p 3100:3100 -v /media/storage:/media/storage -v /home/trout/.p2pvps/logs/connect-client/:/usr/src/app/logs renter-shell 8 | -------------------------------------------------------------------------------- /client/vm/flash-storage/lib/stopImage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker stop renter-shell 3 | -------------------------------------------------------------------------------- /client/vm/flash-storage/lib/write-files.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Chris Troutner & P2PVPS.org 3 | * Licensing Information: MIT License 4 | * 5 | * This program writes out the Dockerfile and various configuration files. 6 | */ 7 | 8 | "use strict"; 9 | 10 | const fs = require("fs"); 11 | 12 | class WriteFiles { 13 | constructor(deviceConfig) { 14 | this.port = ""; 15 | this.username = ""; 16 | this.password = ""; 17 | this.deviceId = deviceConfig.deviceId; 18 | this.serverIp = deviceConfig.serverIp; 19 | this.serverPort = deviceConfig.serverPort; 20 | this.sshServer = deviceConfig.sshServer; 21 | this.sshServerPort = deviceConfig.sshServerPort; 22 | } 23 | 24 | // This function writes out the Dockerfile. 25 | writeDockerfile(port, username, password) { 26 | this.port = port; 27 | this.username = username; 28 | this.password = password; 29 | 30 | return new Promise((resolve, reject) => { 31 | const fileString = `FROM ubuntu:16.04 32 | MAINTAINER Chris Troutner 33 | RUN apt-get -y update 34 | RUN apt-get install -y openssh-server 35 | RUN apt-get install -y nano 36 | RUN apt-get install -y ssh 37 | RUN mkdir /var/run/sshd 38 | RUN sed 's@sessions*requireds*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd 39 | ENV NOTVISIBLE "in users profile" 40 | RUN echo "export VISIBLE=now" >> /etc/profile 41 | RUN apt-get install -y curl 42 | RUN curl -sL https://deb.nodesource.com/setup_8.x -o nodesource_setup.sh 43 | RUN bash nodesource_setup.sh 44 | RUN apt-get install -y nodejs 45 | RUN apt-get install -y build-essential 46 | RUN apt-get install -y sudo 47 | RUN apt-get install -y git 48 | WORKDIR /root 49 | VOLUME /usr/src/app/logs 50 | COPY package.json package.json 51 | RUN npm install 52 | EXPOSE 3100 53 | COPY dummyapp.js dummyapp.js 54 | COPY finalsetup finalsetup 55 | COPY connect-client.js connect-client.js 56 | COPY package.json package.json 57 | COPY config.json config.json 58 | RUN chmod 775 finalsetup 59 | VOLUME /media/storage 60 | RUN useradd -ms /bin/bash ${this.username} 61 | RUN echo ${this.username}:${this.password} | chpasswd 62 | RUN chown -R ${this.username} /media/storage 63 | RUN adduser ${this.username} sudo 64 | EXPOSE ${this.port} 65 | #ENTRYPOINT [\"./finalsetup\", \"node\", \"dummyapp.js\"] 66 | RUN echo ${this.username} > username 67 | ENTRYPOINT ["./finalsetup", "node", "connect-client.js"] 68 | `; 69 | 70 | fs.writeFile("./output-files/Dockerfile", fileString, function(err) { 71 | if (err) { 72 | debugger; 73 | console.error("Error while trying to write file: ", err); 74 | reject(err); 75 | } else { 76 | console.log("Dockerfile written successfully!"); 77 | resolve(); 78 | } 79 | }); 80 | }); 81 | } 82 | 83 | // writeClientConfig writes out the config.json file. 84 | writeClientConfig() { 85 | //debugger; 86 | 87 | return new Promise((resolve, reject) => { 88 | const fileJSON = { 89 | deviceId: this.deviceId, 90 | serverIp: this.serverIp, 91 | serverPort: this.serverPort, 92 | sshServer: this.sshServer, 93 | sshServerPort: this.sshServerPort, 94 | sshTunnelPort: this.port, 95 | }; 96 | 97 | fs.writeFile("./output-files/config.json", JSON.stringify(fileJSON, null, 2), function(err) { 98 | if (err) { 99 | debugger; 100 | console.error("Error while trying to write config.json file: ", err); 101 | reject(err); 102 | } else { 103 | console.log("config.json written successfully!"); 104 | resolve(); 105 | } 106 | }); 107 | }); 108 | } 109 | } 110 | 111 | module.exports = WriteFiles; 112 | -------------------------------------------------------------------------------- /client/vm/flash-storage/log/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/P2PVPS/p2pvps-client/d424e89c5497d6039cef0a03d02ed0df260d28a0/client/vm/flash-storage/log/README.md -------------------------------------------------------------------------------- /client/vm/flash-storage/output-files/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:17.04 2 | MAINTAINER Chris Troutner 3 | RUN apt-get -y update 4 | RUN apt-get install -y openssh-server 5 | RUN apt-get install -y nano 6 | RUN apt-get install -y ssh 7 | RUN mkdir /var/run/sshd 8 | RUN sed 's@sessions*requireds*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd 9 | ENV NOTVISIBLE "in users profile" 10 | RUN echo "export VISIBLE=now" >> /etc/profile 11 | RUN apt-get install -y curl 12 | RUN curl -sL https://deb.nodesource.com/setup_8.x -o nodesource_setup.sh 13 | RUN bash nodesource_setup.sh 14 | RUN apt-get install -y nodejs 15 | RUN apt-get install -y build-essential 16 | WORKDIR /root 17 | VOLUME /usr/src/app/logs 18 | COPY package.json package.json 19 | RUN npm install 20 | EXPOSE 3100 21 | COPY dummyapp.js dummyapp.js 22 | COPY finalsetup finalsetup 23 | COPY connect-client.js connect-client.js 24 | COPY package.json package.json 25 | COPY config.json config.json 26 | RUN chmod 775 finalsetup 27 | VOLUME /media/storage 28 | RUN useradd -ms /bin/bash 2UWGUX29CT 29 | RUN echo 2UWGUX29CT:msjceOaCYb | chpasswd 30 | EXPOSE 6165 31 | #ENTRYPOINT ["./finalsetup", "node", "dummyapp.js"] 32 | ENTRYPOINT ["./finalsetup", "node", "connect-client.js"] 33 | -------------------------------------------------------------------------------- /client/vm/flash-storage/output-files/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "deviceId": "5a4567a2236fa30097d94420", 3 | "serverIp": "p2pvps.net", 4 | "serverPort": 3001, 5 | "sshServer": "p2pvps.net", 6 | "sshServerPort": 6100, 7 | "sshTunnelPort": "6165" 8 | } -------------------------------------------------------------------------------- /client/vm/flash-storage/output-files/connect-client.js: -------------------------------------------------------------------------------- 1 | /* 2 | * The primary functions of this program are to: 3 | * -Establish a reverse SSH tunnel with the SSH server 4 | * -Run a heartbeat API that updates a timestamp on the P2P VPS server every 2 minutes. 5 | */ 6 | 7 | "use strict"; 8 | 9 | const tunnel = require("reverse-tunnel-ssh"); //tunnel is a ssh2 clientConnection object 10 | const request = require("request"); //Used for CURL style requests. 11 | const express = require("express"); 12 | const winston = require("winston"); 13 | 14 | global.config = false; 15 | 16 | try { 17 | global.config = require("./config.json"); 18 | winston.info(`Connecting device to P2P VPS server with ID ${global.config.deviceId}`); 19 | } catch (err) { 20 | console.error("Could not open the config.json file!", err); 21 | process.exit(1); 22 | } 23 | 24 | // Set up the Winston logging. 25 | winston.add(winston.transports.File, { 26 | filename: "/usr/src/app/logs/connect-client.log", 27 | maxFiles: 1, 28 | colorize: false, 29 | timestamp: true, 30 | datePattern: ".yyyy-MM-ddTHH-mm", 31 | maxsize: 1000000, 32 | json: false, 33 | }); 34 | 35 | // Set the logging level. 36 | winston.level = "debug"; 37 | 38 | // Start first line of the log. 39 | const now = new Date(); 40 | winston.log("info", `Application starting at ${now}`); 41 | 42 | const app = express(); 43 | const port = 4010; 44 | 45 | /* 46 | * Use Handlebars for templating 47 | */ 48 | const exphbs = require("express3-handlebars"); 49 | //let hbs; 50 | 51 | // For gzip compression 52 | //app.use(express.compress()); 53 | 54 | /* 55 | * Config for Production and Development 56 | */ 57 | app.engine( 58 | "handlebars", 59 | exphbs({ 60 | // Default Layout and locate layouts and partials 61 | defaultLayout: "main", 62 | layoutsDir: "views/layouts/", 63 | partialsDir: "views/partials/", 64 | }) 65 | ); 66 | 67 | // Locate the views 68 | app.set("views", `${__dirname}/views`); 69 | 70 | // Locate the assets 71 | app.use(express.static(`${__dirname}/assets`)); 72 | 73 | // Set Handlebars 74 | app.set("view engine", "handlebars"); 75 | 76 | // Index Page 77 | app.get("/", function(request, response, next) { 78 | response.render("index"); 79 | }); 80 | 81 | /* Start up the Express web server */ 82 | app.listen(process.env.PORT || port); 83 | winston.info(`P2P VPS Keep Alive timer started on port ${port}`); 84 | 85 | // Check in with the P2P VPS server every 2 minutes with this API. 86 | // This lets the server know the Client is still connected. 87 | const checkInTimer = setInterval(function() { 88 | //Register with the server by sending the benchmark data. 89 | request.get( 90 | { 91 | url: `http://${global.config.serverIp}:${global.config.serverPort}/api/deviceCheckIn/${ 92 | global.config.deviceId 93 | }`, 94 | // form: obj 95 | }, 96 | function(error, response, body) { 97 | try { 98 | debugger; 99 | //If the request was successfull, the server will respond with username, password, and port to be 100 | //used to build the Docker file. 101 | if (!error && response.statusCode === 200) { 102 | debugger; 103 | 104 | //Convert the data from a string into a JSON object. 105 | const data = JSON.parse(body); //Convert the returned JSON to a JSON string. 106 | 107 | if (data.success) { 108 | const now = new Date(); 109 | winston.info( 110 | `Checked in for device ${global.config.deviceId} at ${now.toLocaleString()}` 111 | ); 112 | } else { 113 | console.error(`Check-in failed for ${global.config.deviceId}`); 114 | } 115 | } else { 116 | debugger; 117 | 118 | // Server responded with some other status than 200. 119 | if (response) { 120 | if (response.statusCode !== 200) 121 | console.error("P2P VPS server rejected checking: ", response); 122 | } else if (error.code === "EHOSTUNREACH" || error.code === "ECONNREFUSED") { 123 | // Could not connect to the server. 124 | debugger; 125 | winston.info(`Warning: Could not connect to server at ${now.toLocaleString()}`); 126 | return; 127 | } else { 128 | console.error( 129 | "Server responded with error when trying to register the device: ", 130 | error 131 | ); 132 | console.error( 133 | "Ensure the ID in your deviceGUID.json file matches the ID in the Owned Devices section of the marketplace." 134 | ); 135 | } 136 | } 137 | } catch (err) { 138 | winston.info(`connect-client.js exiting with error: ${err}`); 139 | } 140 | } 141 | ); 142 | }, 120000); 143 | 144 | // Establish a reverse SSH connection. 145 | function createTunnel() { 146 | try { 147 | const conn = tunnel( 148 | { 149 | host: global.config.sshServer, 150 | port: global.config.sshServerPort, //The SSH port on the server. 151 | username: "sshuser", 152 | password: "sshuserpassword", 153 | dstHost: "0.0.0.0", // bind to all IPv4 interfaces 154 | dstPort: global.config.sshTunnelPort, //The new port that will be opened 155 | //srcHost: '127.0.0.1', // default 156 | srcPort: 3100, // The port on the Pi to tunnel to. 157 | //readyTimeout: 20000, 158 | //debug: myDebug, 159 | }, 160 | function(error, clientConnection) { 161 | if (error) { 162 | winston.info("There was an error in connect-client.js/tunnel()!"); 163 | console.error(JSON.stringify(error, null, 2)); 164 | } else { 165 | winston.info( 166 | `Reverse tunnel established on destination port ${global.config.sshTunnelPort}` 167 | ); 168 | } 169 | } 170 | ); 171 | 172 | conn.on("error", function(error) { 173 | debugger; 174 | 175 | // Could not connect to the internet. 176 | if (error.level === "client-timeout" || error.level === "client-socket") { 177 | debugger; 178 | winston.info("Warning, could not connect to SSH server. Waiting before retry."); 179 | } else { 180 | console.error("Error with connect-client.js: "); 181 | console.error(JSON.stringify(error, null, 2)); 182 | } 183 | 184 | // Try again in a short while. 185 | setTimeout(function() { 186 | createTunnel(); 187 | }, 30000); 188 | }); 189 | 190 | conn.on("close", function(val) { 191 | winston.info("SSH connection for connect-client.js was closed."); 192 | }); 193 | 194 | conn.on("end", function(val) { 195 | winston.info("SSH connection for connect-client ended."); 196 | }); 197 | } catch (err) { 198 | console.error("I caught the error!"); 199 | console.error(JSON.stringify(err, null, 2)); 200 | } 201 | } 202 | 203 | // Execute the first time. 204 | setTimeout(function() { 205 | createTunnel(); 206 | }, 5000); 207 | 208 | function myDebug(val) { 209 | winston.info(val); 210 | } 211 | -------------------------------------------------------------------------------- /client/vm/flash-storage/output-files/dummyapp.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | 3 | var app = express(); 4 | var port = 3125; 5 | 6 | /* 7 | * Config for Production and Development 8 | */ 9 | /* 10 | app.engine('handlebars', exphbs({ 11 | // Default Layout and locate layouts and partials 12 | defaultLayout: 'main', 13 | layoutsDir: 'views/layouts/', 14 | partialsDir: 'views/partials/' 15 | })); 16 | */ 17 | 18 | // Locate the views 19 | //app.set('views', __dirname + '/views'); 20 | 21 | // Locate the assets 22 | //app.use(express.static(__dirname + '/assets')); 23 | 24 | 25 | // Set Handlebars 26 | app.set('view engine', 'handlebars'); 27 | 28 | 29 | /* Start up the Express web server */ 30 | app.listen(process.env.PORT || port); 31 | console.log('Express started on port ' + port); 32 | 33 | -------------------------------------------------------------------------------- /client/vm/flash-storage/output-files/example-Dockerfile: -------------------------------------------------------------------------------- 1 | FROM resin/rpi-raspbian 2 | MAINTAINER Chris Troutner 3 | RUN apt-get -y update 4 | RUN apt-get install -y openssh-server 5 | RUN apt-get install -y nano 6 | RUN apt-get install -y ssh 7 | RUN mkdir /var/run/sshd 8 | RUN sed 's@sessions*requireds*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd 9 | ENV NOTVISIBLE "in users profile" 10 | RUN echo "export VISIBLE=now" >> /etc/profile 11 | RUN curl -sL https://deb.nodesource.com/setup_8.x -o nodesource_setup.sh 12 | RUN bash nodesource_setup.sh 13 | RUN apt-get install -y nodejs 14 | RUN apt-get install -y build-essential 15 | WORKDIR /root 16 | COPY package.json package.json 17 | RUN npm install 18 | EXPOSE 3100 19 | COPY dummyapp.js dummyapp.js 20 | COPY finalsetup finalsetup 21 | COPY connect-client.js connect-client.js 22 | COPY package.json package.json 23 | COPY config.json config.json 24 | RUN chmod 775 finalsetup 25 | RUN useradd -ms /bin/bash bbb 26 | RUN echo bbb:ccc | chpasswd 27 | EXPOSE aaa 28 | ENTRYPOINT ["./finalsetup", "node", "connect-client.js"] 29 | -------------------------------------------------------------------------------- /client/vm/flash-storage/output-files/finalsetup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Copyright 2017 RPiOVN.org 4 | #Licensing Information: http://rpiovn.org/license 5 | 6 | 7 | #This script should be edited to execute any merge scripts needed to 8 | #merge plugins and theme files before starting ConnextCMS/KeystoneJS. 9 | 10 | echo Running finalsetup script 11 | 12 | #Run any merge scripts to install plugins or site templates. 13 | #public/merge-site-plugin-files 14 | 15 | #cd ~/myCMS 16 | 17 | #Copy the /public directory for nginx 18 | #cp -r public/* ~/public 19 | 20 | #["/usr/sbin/sshd", "-D", "-p", "4100"] 21 | /usr/sbin/sshd -D -p 3100 & 22 | 23 | #Change permissions so use can write to persistant storage. 24 | username=$(head -n 1 username) 25 | sudo chown -R $username /media/storage 26 | 27 | 28 | #Launch KeystoneJS and ConnextCMS 29 | exec "$@" 30 | -------------------------------------------------------------------------------- /client/vm/flash-storage/output-files/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "connect-client.js", 3 | "version": "1.0.0", 4 | "description": "Connects the Client to the P2P VPS server", 5 | "main": "connectClient.js", 6 | "dependencies": { 7 | "execa": "^0.8.0", 8 | "express": "^4.16.2", 9 | "express3-handlebars": "^0.5.2", 10 | "node-promise": "^0.5.12", 11 | "request": "^2.83.0", 12 | "reverse-tunnel-ssh": "^1.1.0", 13 | "winston": "^2.4.0" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/RPiOVN/p2pvps-client" 18 | }, 19 | "devDependencies": {}, 20 | "scripts": { 21 | "test": "echo \"Error: no test specified\" && exit 1" 22 | }, 23 | "author": "chris.troutner@gmail.com", 24 | "license": "MIT" 25 | } 26 | -------------------------------------------------------------------------------- /client/vm/flash-storage/p2p-vps-client.js: -------------------------------------------------------------------------------- 1 | /* 2 | This is the primary 'governor' application that drives a Client device and allows it to communicate 3 | with a P2P VPS Server. The scope of this application covers: 4 | 5 | * It reads the device-config.json file and registers the Client device with the P2P VPS server. 6 | 7 | * It builds the Docker container with information returned by the server after registration. 8 | 9 | * It launches the Docker container after being built. 10 | 11 | * It sends a heartbeat signal to the P2P VPS server every 10 minutes. The server responds with an 12 | expiration date. 13 | * (Maybe I can also send benchmark data to the server?) 14 | 15 | * When the expiration date is reached, or the Server can not be reached after 30 minutes, the governor 16 | software stops the Docker container and wipes the flash drive. It then reregisters itself with the 17 | P2P VPS marketplace. 18 | 19 | * If the Client can not make contact with the Server, it quietly retries to make contact every 2 minutes. 20 | 21 | Specifications for this program can be found here: 22 | https://github.com/P2PVPS/p2pvps-server/blob/master/specifications/client-specification.md 23 | */ 24 | 25 | /* 26 | * Copyright 2017 Chris Troutner & P2PVPS.org 27 | * MIT License. See LICENSE.md for details. 28 | */ 29 | 30 | //This file registers with the server 31 | "use strict"; 32 | 33 | // Express Dependencies 34 | const express = require("express"); 35 | const execa = require("execa"); 36 | const sudo = require("p2pvps-sudo"); 37 | 38 | // Global Variables 39 | const app = express(); 40 | const port = 4000; 41 | 42 | // Read in device-config.json file 43 | let deviceConfig; 44 | try { 45 | deviceConfig = require("./device-config.json"); 46 | console.log(`Registering device ID ${deviceConfig.deviceId}`); 47 | } catch (err) { 48 | const msg = "Could not open the device-config.json file! Exiting."; 49 | console.error(msg, err); 50 | process.exit(1); 51 | } 52 | 53 | // Each type of client shell will have a unique write-files.js library. 54 | const WriteFiles = require("./lib/write-files.js"); 55 | const writeFiles = new WriteFiles(deviceConfig); 56 | 57 | // Initialize the debugging logger. 58 | const Logger = require("../../lib/logger.js"); 59 | const logr = new Logger(deviceConfig); 60 | 61 | // Utility functions for dealing with the P2P VPS server. Shared by all clients. 62 | const P2pVpsServer = require("../../lib/p2p-vps-server.js"); 63 | const p2pVpsServer = new P2pVpsServer(deviceConfig, logr); 64 | 65 | // Create an Express server. Future development will allow serving of webpages and creation of Client API. 66 | const ExpressServer = require("../../lib/express-server.js"); 67 | const expressServer = new ExpressServer(app, port); 68 | expressServer.start(); 69 | 70 | const sudoOptions = { 71 | cachePassword: true, 72 | prompt: "Password for sudo is needed: ", 73 | password: deviceConfig.sudoPassword, 74 | spawnOptions: { 75 | /* other options for spawn */ 76 | }, 77 | }; 78 | 79 | // This is a high-level function used to register this Client with the Server. 80 | // It calls the registration function, writes out the support files, builds the Docker container, 81 | // and launches the Docker container. 82 | function registerDevice() { 83 | logr.debug(`Registering device ${deviceConfig.deviceId}`); 84 | 85 | //Simulate benchmark tests with dummy data. 86 | const now = new Date(); 87 | const deviceSpecs = { 88 | memory: "Fake Test Data", 89 | diskSpace: "Fake Test Data", 90 | processor: "Fake Test Data", 91 | internetSpeed: "Fake Test Data", 92 | checkinTimeStamp: now.toISOString(), 93 | }; 94 | 95 | const config = { 96 | deviceId: deviceConfig.deviceId, 97 | deviceSpecs: deviceSpecs, 98 | }; 99 | 100 | const execaOptions = { 101 | stdout: "inherit", 102 | stderr: "inherit", 103 | //uid: 0, 104 | //gid: 0, 105 | }; 106 | 107 | // Register with the server. 108 | p2pVpsServer 109 | .register(config) 110 | 111 | // Write out support files (Dockerfile, config.json) 112 | .then(clientData => { 113 | //debugger; 114 | 115 | // Save data to a global variable for use in later functions. 116 | global.clientData = clientData.device; 117 | 118 | return ( 119 | // Write out the Dockerfile. 120 | writeFiles 121 | .writeDockerfile( 122 | clientData.device.port, 123 | clientData.device.username, 124 | clientData.device.password) 125 | 126 | // Write out the config file. 127 | .then(() => { 128 | return writeFiles.writeClientConfig(); 129 | }) 130 | 131 | .catch(err => { 132 | logr.error("Problem writing out support files: ", err); 133 | }) 134 | ); 135 | }) 136 | 137 | // Wipe and mount the flash drive 138 | .then(() => { 139 | logr.log("Wiping and mounting persistent storage."); 140 | 141 | // Use the sudo() function to launch the script with sudo privledges. 142 | return new Promise(function(resolve, reject) { 143 | const child = sudo(["./lib/prep-flash-storage"], sudoOptions); 144 | 145 | child.stdout.on("data", function(data) { 146 | logr.log(data.toString()); 147 | }); 148 | 149 | child.on("close", code => { 150 | logr.info(`Storage prep script exited with code ${code}`); 151 | return resolve(); 152 | }); 153 | 154 | child.stderr.on("data", function(data) { 155 | logr.warn(data.toString()); 156 | //logr.error("Error while trying to wipe and mount persistent storage!"); 157 | //logr.error(JSON.stringify(data, null, 2)); 158 | //process.exit(1); 159 | //return reject(); 160 | }); 161 | }); 162 | }) 163 | 164 | // Build the Docker container. 165 | .then(() => { 166 | logr.log("Building Docker Image."); 167 | 168 | return execa("./lib/buildImage", undefined, execaOptions) 169 | .then(result => { 170 | //debugger; 171 | console.log(result.stdout); 172 | }) 173 | .catch(err => { 174 | debugger; 175 | console.error("Error while trying to build Docker image!", err); 176 | logr.error("Error while trying to build Docker image!", err); 177 | logr.error(JSON.stringify(err, null, 2)); 178 | process.exit(1); 179 | }); 180 | }) 181 | 182 | // Run the Docker container 183 | .then(() => { 184 | logr.log("Running the Docker image."); 185 | 186 | return execa("./lib/runImage", undefined, execaOptions) 187 | .then(result => { 188 | //debugger; 189 | logr.log(result.stdout); 190 | }) 191 | .catch(err => { 192 | debugger; 193 | logr.error("Error while trying to run Docker image!"); 194 | logr.error(JSON.stringify(err, null, 2)); 195 | process.exit(1); 196 | }); 197 | }) 198 | 199 | .then(() => { 200 | logr.log("Docker image has been built and is running."); 201 | 202 | // Begin timer to check expiration. 203 | p2pVpsServer.startExpirationTimer(registerDevice); 204 | }) 205 | 206 | .catch(err => { 207 | logr.error("Error in main program: ", err); 208 | process.exit(1); 209 | }); 210 | } 211 | registerDevice(); 212 | -------------------------------------------------------------------------------- /client/vm/simple/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock = false 2 | -------------------------------------------------------------------------------- /client/vm/simple/README.md: -------------------------------------------------------------------------------- 1 | # Simple VPS Client 2 | This is the simplest possible implementation of a Virtual Private Server (VPS). It spins up 3 | a Ubuntu-based Docker container with SSH shell access. There is no persistent storage, 4 | so everything is held in memory and deleted if the device is rebooted. The SSH user also 5 | does not have sudo privileges, so they're strongly restricted in what they can do. 6 | 7 | However, by keeping it simple, this client is preferred for testing. If you're a beginner at 8 | setting up a P2P VPS client, you should start by following the directions below. 9 | 10 | 11 | ## Installation 12 | These instructions assume you are starting with a fresh installation of Ubuntu 16.04 or newer. 13 | 14 | You will also need to register an account at: https://p2pvps.net/createaccount 15 | 16 | **Note** this is where you will get the device GUID mentioned in step 9 below. 17 | 18 | 19 | ### Device Configuration 20 | 21 | 1. This is a great time to update the software on the device, including any security patches. 22 | ``` 23 | sudo apt-get -y update 24 | 25 | sudo apt-get -y upgrade 26 | ``` 27 | 28 | 2. This installation also assumes you are using node v8 or higher. You can install it with: 29 | ``` 30 | sudo apt-get -y remove nodejs 31 | curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - 32 | sudo apt-get install -y nodejs build-essential 33 | ``` 34 | 35 | 3. You'll also need to install Docker. Prior to running the instructions below, 36 | this is a great time to reboot your device. It seems to prevent occasional errors 37 | with installing Docker. 38 | 39 | `curl -sSL https://get.docker.com | sh` 40 | 41 | 4. Follow the on-screen instructions to add your user to the docker group. 42 | You'll need to open a new terminal after entering this instruction: 43 | 44 | `sudo usermod -aG docker $USER` 45 | 46 | 5. (optional) create a directory for your node applications, like this: 47 | ``` 48 | mkdir node 49 | cd node 50 | ``` 51 | 52 | 6. Clone this repository: 53 | 54 | `git clone https://github.com/P2PVPS/p2pvps-client` 55 | 56 | 7. Setup the Client libraries by running: 57 | ``` 58 | cd p2pvps-client/client 59 | npm install 60 | ``` 61 | 62 | 8. Change into the VM simple client directory: 63 | 64 | `cd vm/simple/` 65 | 66 | 9. Get your device GUID from [the P2P VPS marketplace](http://p2pvps.net). You'll need to create an account. The GUID is provided in 67 | the *Owned Devices view* by clicking the *+Add New Device* button. Paste this GUID into the `device-config.json` file. 68 | 69 | 10. Launch the simple client. The first time will take a while as it will need to download and 70 | build several Docker containers: 71 | 72 | `sudo node p2p-vps-client.js` 73 | 74 | That's it! Once the application presents the message `Docker image has been built and is running.`, 75 | your device is now connected to the P2P VPS server and listed on the market for rent. 76 | -------------------------------------------------------------------------------- /client/vm/simple/device-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "deviceId": "5ac90cabbd630b005fac3222", 3 | "loggingPath": "/home/trout/.p2pvps/logs/p2p-vps-client.log", 4 | "serverIp": "p2pvps.net", 5 | "serverPort": 3001, 6 | "sshServer": "p2pvps.net", 7 | "sshServerPort": 6100 8 | } 9 | -------------------------------------------------------------------------------- /client/vm/simple/lib/buildImage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cp ../../lib/connect-client.js ./output-files/ 3 | cd output-files 4 | docker build -t renter-shell . 5 | -------------------------------------------------------------------------------- /client/vm/simple/lib/cleanupImages: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Remove all untagged docker images. 4 | docker rmi $(docker images | grep "^" | awk '{print $3}') 5 | -------------------------------------------------------------------------------- /client/vm/simple/lib/enterImage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker container run --name renter-shell --rm -p 3100:3100 -p 6100:6100 -it renter-shell /bin/bash 3 | -------------------------------------------------------------------------------- /client/vm/simple/lib/runImage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Copy the SSH config file to stop the irritating key prompt 4 | #cp config ~/.ssh/ 5 | 6 | #Run the docker image 7 | docker run -d --rm --name renter-shell -p 6100:6100 -p 3100:3100 -v /home/trout/.p2pvps/logs/connect-client/:/usr/src/app/logs renter-shell 8 | -------------------------------------------------------------------------------- /client/vm/simple/lib/stopImage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker stop renter-shell 3 | -------------------------------------------------------------------------------- /client/vm/simple/lib/write-files.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Chris Troutner & P2PVPS.org 3 | * Licensing Information: MIT License 4 | * 5 | * This program writes out the Dockerfile and various configuration files. 6 | */ 7 | 8 | "use strict"; 9 | 10 | const fs = require("fs"); 11 | 12 | class WriteFiles { 13 | constructor(deviceConfig) { 14 | this.port = ""; 15 | this.username = ""; 16 | this.password = ""; 17 | this.deviceId = deviceConfig.deviceId; 18 | this.serverIp = deviceConfig.serverIp; 19 | this.serverPort = deviceConfig.serverPort; 20 | this.sshServer = deviceConfig.sshServer; 21 | this.sshServerPort = deviceConfig.sshServerPort; 22 | } 23 | 24 | // This function writes out the Dockerfile. 25 | writeDockerfile(port, username, password) { 26 | this.port = port; 27 | this.username = username; 28 | this.password = password; 29 | 30 | return new Promise((resolve, reject) => { 31 | const fileString = `FROM ubuntu:16.04 32 | MAINTAINER Chris Troutner 33 | RUN apt-get -y update 34 | RUN apt-get install -y openssh-server 35 | RUN apt-get install -y nano 36 | RUN apt-get install -y ssh 37 | RUN mkdir /var/run/sshd 38 | RUN sed 's@sessions*requireds*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd 39 | ENV NOTVISIBLE "in users profile" 40 | RUN echo "export VISIBLE=now" >> /etc/profile 41 | RUN apt-get install -y curl 42 | RUN curl -sL https://deb.nodesource.com/setup_8.x -o nodesource_setup.sh 43 | RUN bash nodesource_setup.sh 44 | RUN apt-get install -y nodejs 45 | RUN apt-get install -y build-essential 46 | RUN apt-get install -y sudo 47 | RUN apt-get install -y net-tools 48 | RUN apt-get install -y iputils-ping 49 | WORKDIR /root 50 | VOLUME /usr/src/app/logs 51 | COPY package.json package.json 52 | RUN npm install 53 | EXPOSE 3100 54 | COPY dummyapp.js dummyapp.js 55 | COPY finalsetup finalsetup 56 | COPY connect-client.js connect-client.js 57 | COPY package.json package.json 58 | COPY config.json config.json 59 | RUN chmod 775 finalsetup 60 | RUN useradd -ms /bin/bash ${this.username} 61 | RUN echo ${this.username}:${this.password} | chpasswd 62 | RUN adduser ${this.username} sudo 63 | EXPOSE ${this.port} 64 | #ENTRYPOINT [\"./finalsetup\", \"node\", \"dummyapp.js\"] 65 | ENTRYPOINT ["./finalsetup", "node", "connect-client.js"] 66 | `; 67 | 68 | fs.writeFile("./output-files/Dockerfile", fileString, function(err) { 69 | if (err) { 70 | debugger; 71 | console.error("Error while trying to write file: ", err); 72 | reject(err); 73 | } else { 74 | console.log("Dockerfile written successfully!"); 75 | resolve(); 76 | } 77 | }); 78 | }); 79 | } 80 | 81 | // writeClientConfig writes out the config.json file. 82 | writeClientConfig() { 83 | //debugger; 84 | 85 | return new Promise((resolve, reject) => { 86 | const fileJSON = { 87 | deviceId: this.deviceId, 88 | serverIp: this.serverIp, 89 | serverPort: this.serverPort, 90 | sshServer: this.sshServer, 91 | sshServerPort: this.sshServerPort, 92 | sshTunnelPort: this.port, 93 | }; 94 | 95 | fs.writeFile("./output-files/config.json", JSON.stringify(fileJSON, null, 2), function(err) { 96 | if (err) { 97 | debugger; 98 | console.error("Error while trying to write config.json file: ", err); 99 | reject(err); 100 | } else { 101 | console.log("config.json written successfully!"); 102 | resolve(); 103 | } 104 | }); 105 | }); 106 | } 107 | } 108 | 109 | module.exports = WriteFiles; 110 | -------------------------------------------------------------------------------- /client/vm/simple/output-files/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | MAINTAINER Chris Troutner 3 | RUN apt-get -y update 4 | RUN apt-get install -y openssh-server 5 | RUN apt-get install -y nano 6 | RUN apt-get install -y ssh 7 | RUN mkdir /var/run/sshd 8 | RUN sed 's@sessions*requireds*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd 9 | ENV NOTVISIBLE "in users profile" 10 | RUN echo "export VISIBLE=now" >> /etc/profile 11 | RUN apt-get install -y curl 12 | RUN curl -sL https://deb.nodesource.com/setup_8.x -o nodesource_setup.sh 13 | RUN bash nodesource_setup.sh 14 | RUN apt-get install -y nodejs 15 | RUN apt-get install -y build-essential 16 | RUN apt-get install -y sudo 17 | RUN apt-get install -y net-tools 18 | RUN apt-get install -y iputils-ping 19 | WORKDIR /root 20 | VOLUME /usr/src/app/logs 21 | COPY package.json package.json 22 | RUN npm install 23 | EXPOSE 3100 24 | COPY dummyapp.js dummyapp.js 25 | COPY finalsetup finalsetup 26 | COPY connect-client.js connect-client.js 27 | COPY package.json package.json 28 | COPY config.json config.json 29 | RUN chmod 775 finalsetup 30 | RUN useradd -ms /bin/bash miCLmFfDLS 31 | RUN echo miCLmFfDLS:is0Ag3CGui | chpasswd 32 | RUN adduser miCLmFfDLS sudo 33 | EXPOSE 6017 34 | #ENTRYPOINT ["./finalsetup", "node", "dummyapp.js"] 35 | ENTRYPOINT ["./finalsetup", "node", "connect-client.js"] 36 | -------------------------------------------------------------------------------- /client/vm/simple/output-files/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "deviceId": "5ac90cabbd630b005fac3222", 3 | "serverIp": "p2pvps.net", 4 | "serverPort": 3001, 5 | "sshServer": "p2pvps.net", 6 | "sshServerPort": 6100, 7 | "sshTunnelPort": 6017 8 | } -------------------------------------------------------------------------------- /client/vm/simple/output-files/connect-client.js: -------------------------------------------------------------------------------- 1 | /* 2 | * The primary functions of this program are to: 3 | * -Establish a reverse SSH tunnel with the SSH server 4 | * -Run a heartbeat API that updates a timestamp on the P2P VPS server every 2 minutes. 5 | */ 6 | 7 | "use strict"; 8 | 9 | const tunnel = require("reverse-tunnel-ssh"); //tunnel is a ssh2 clientConnection object 10 | const request = require("request"); //Used for CURL style requests. 11 | const express = require("express"); 12 | const winston = require("winston"); 13 | 14 | global.config = false; 15 | 16 | try { 17 | global.config = require("./config.json"); 18 | winston.info(`Connecting device to P2P VPS server with ID ${global.config.deviceId}`); 19 | } catch (err) { 20 | console.error("Could not open the config.json file!", err); 21 | process.exit(1); 22 | } 23 | 24 | // Set up the Winston logging. 25 | winston.add(winston.transports.File, { 26 | filename: "/usr/src/app/logs/connect-client.log", 27 | maxFiles: 1, 28 | colorize: false, 29 | timestamp: true, 30 | datePattern: ".yyyy-MM-ddTHH-mm", 31 | maxsize: 1000000, 32 | json: false, 33 | }); 34 | 35 | // Set the logging level. 36 | winston.level = "debug"; 37 | 38 | // Start first line of the log. 39 | const now = new Date(); 40 | winston.log("info", `Application starting at ${now}`); 41 | 42 | const app = express(); 43 | const port = 4010; 44 | 45 | /* 46 | * Use Handlebars for templating 47 | */ 48 | const exphbs = require("express3-handlebars"); 49 | //let hbs; 50 | 51 | // For gzip compression 52 | //app.use(express.compress()); 53 | 54 | /* 55 | * Config for Production and Development 56 | */ 57 | app.engine( 58 | "handlebars", 59 | exphbs({ 60 | // Default Layout and locate layouts and partials 61 | defaultLayout: "main", 62 | layoutsDir: "views/layouts/", 63 | partialsDir: "views/partials/", 64 | }) 65 | ); 66 | 67 | // Locate the views 68 | app.set("views", `${__dirname}/views`); 69 | 70 | // Locate the assets 71 | app.use(express.static(`${__dirname}/assets`)); 72 | 73 | // Set Handlebars 74 | app.set("view engine", "handlebars"); 75 | 76 | // Index Page 77 | app.get("/", function(request, response, next) { 78 | response.render("index"); 79 | }); 80 | 81 | /* Start up the Express web server */ 82 | app.listen(process.env.PORT || port); 83 | winston.info(`P2P VPS Keep Alive timer started on port ${port}`); 84 | 85 | // Check in with the P2P VPS server every 2 minutes with this API. 86 | // This lets the server know the Client is still connected. 87 | const checkInTimer = setInterval(function() { 88 | //Register with the server by sending the benchmark data. 89 | request.get( 90 | { 91 | url: `http://${global.config.serverIp}:${global.config.serverPort}/api/client/checkin/${ 92 | global.config.deviceId 93 | }`, 94 | // form: obj 95 | }, 96 | function(error, response, body) { 97 | try { 98 | debugger; 99 | //If the request was successfull, the server will respond with username, password, and port to be 100 | //used to build the Docker file. 101 | if (!error && response.statusCode === 200) { 102 | debugger; 103 | 104 | //Convert the data from a string into a JSON object. 105 | const data = JSON.parse(body); //Convert the returned JSON to a JSON string. 106 | 107 | if (data.success) { 108 | const now = new Date(); 109 | winston.info( 110 | `Checked in for device ${global.config.deviceId} at ${now.toLocaleString()}` 111 | ); 112 | } else { 113 | console.error(`Check-in failed for ${global.config.deviceId}`); 114 | } 115 | } else { 116 | debugger; 117 | 118 | // Server responded with some other status than 200. 119 | if (response) { 120 | if (response.statusCode !== 200) 121 | console.error("P2P VPS server rejected checking: ", response); 122 | } else if (error.code === "EHOSTUNREACH" || error.code === "ECONNREFUSED") { 123 | // Could not connect to the server. 124 | debugger; 125 | winston.info(`Warning: Could not connect to server at ${now.toLocaleString()}`); 126 | return; 127 | } else { 128 | console.error( 129 | "Server responded with error when trying to register the device: ", 130 | error 131 | ); 132 | console.error( 133 | "Ensure the ID in your deviceGUID.json file matches the ID in the Owned Devices section of the marketplace." 134 | ); 135 | } 136 | } 137 | } catch (err) { 138 | winston.info(`connect-client.js exiting with error: ${err}`); 139 | } 140 | } 141 | ); 142 | }, 120000); 143 | 144 | // Establish a reverse SSH connection. 145 | function createTunnel() { 146 | try { 147 | const conn = tunnel( 148 | { 149 | host: global.config.sshServer, 150 | port: global.config.sshServerPort, //The SSH port on the server. 151 | username: "sshuser", 152 | password: "sshuserpassword", 153 | dstHost: "0.0.0.0", // bind to all IPv4 interfaces 154 | dstPort: global.config.sshTunnelPort, //The new port that will be opened 155 | //srcHost: '127.0.0.1', // default 156 | srcPort: 3100, // The port on the Pi to tunnel to. 157 | //readyTimeout: 20000, 158 | //debug: myDebug, 159 | }, 160 | function(error, clientConnection) { 161 | if (error) { 162 | winston.info("There was an error in connect-client.js/tunnel()!"); 163 | console.error(JSON.stringify(error, null, 2)); 164 | } else { 165 | winston.info( 166 | `Reverse tunnel established on destination port ${global.config.sshTunnelPort}` 167 | ); 168 | } 169 | } 170 | ); 171 | 172 | conn.on("error", function(error) { 173 | debugger; 174 | 175 | // Could not connect to the internet. 176 | if (error.level === "client-timeout" || error.level === "client-socket") { 177 | debugger; 178 | winston.info("Warning, could not connect to SSH server. Waiting before retry."); 179 | } else { 180 | console.error("Error with connect-client.js: "); 181 | console.error(JSON.stringify(error, null, 2)); 182 | } 183 | 184 | // Try again in a short while. 185 | setTimeout(function() { 186 | createTunnel(); 187 | }, 30000); 188 | }); 189 | 190 | conn.on("close", function(val) { 191 | winston.info("SSH connection for connect-client.js was closed."); 192 | }); 193 | 194 | conn.on("end", function(val) { 195 | winston.info("SSH connection for connect-client ended."); 196 | }); 197 | } catch (err) { 198 | console.error("I caught the error!"); 199 | console.error(JSON.stringify(err, null, 2)); 200 | } 201 | } 202 | 203 | // Execute the first time. 204 | setTimeout(function() { 205 | createTunnel(); 206 | }, 5000); 207 | 208 | function myDebug(val) { 209 | winston.info(val); 210 | } 211 | -------------------------------------------------------------------------------- /client/vm/simple/output-files/dummyapp.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | 3 | var app = express(); 4 | var port = 3125; 5 | 6 | /* 7 | * Config for Production and Development 8 | */ 9 | /* 10 | app.engine('handlebars', exphbs({ 11 | // Default Layout and locate layouts and partials 12 | defaultLayout: 'main', 13 | layoutsDir: 'views/layouts/', 14 | partialsDir: 'views/partials/' 15 | })); 16 | */ 17 | 18 | // Locate the views 19 | //app.set('views', __dirname + '/views'); 20 | 21 | // Locate the assets 22 | //app.use(express.static(__dirname + '/assets')); 23 | 24 | 25 | // Set Handlebars 26 | app.set('view engine', 'handlebars'); 27 | 28 | 29 | /* Start up the Express web server */ 30 | app.listen(process.env.PORT || port); 31 | console.log('Express started on port ' + port); 32 | 33 | -------------------------------------------------------------------------------- /client/vm/simple/output-files/example-Dockerfile: -------------------------------------------------------------------------------- 1 | FROM resin/rpi-raspbian 2 | MAINTAINER Chris Troutner 3 | RUN apt-get -y update 4 | RUN apt-get install -y openssh-server 5 | RUN apt-get install -y nano 6 | RUN apt-get install -y ssh 7 | RUN mkdir /var/run/sshd 8 | RUN sed 's@sessions*requireds*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd 9 | ENV NOTVISIBLE "in users profile" 10 | RUN echo "export VISIBLE=now" >> /etc/profile 11 | RUN curl -sL https://deb.nodesource.com/setup_8.x -o nodesource_setup.sh 12 | RUN bash nodesource_setup.sh 13 | RUN apt-get install -y nodejs 14 | RUN apt-get install -y build-essential 15 | WORKDIR /root 16 | COPY package.json package.json 17 | RUN npm install 18 | EXPOSE 3100 19 | COPY dummyapp.js dummyapp.js 20 | COPY finalsetup finalsetup 21 | COPY connect-client.js connect-client.js 22 | COPY package.json package.json 23 | COPY config.json config.json 24 | RUN chmod 775 finalsetup 25 | RUN useradd -ms /bin/bash bbb 26 | RUN echo bbb:ccc | chpasswd 27 | EXPOSE aaa 28 | ENTRYPOINT ["./finalsetup", "node", "connect-client.js"] 29 | -------------------------------------------------------------------------------- /client/vm/simple/output-files/finalsetup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Copyright 2017 RPiOVN.org 4 | #Licensing Information: http://rpiovn.org/license 5 | 6 | 7 | #This script should be edited to execute any merge scripts needed to 8 | #merge plugins and theme files before starting ConnextCMS/KeystoneJS. 9 | 10 | echo Running finalsetup script 11 | 12 | #Run any merge scripts to install plugins or site templates. 13 | #public/merge-site-plugin-files 14 | 15 | #cd ~/myCMS 16 | 17 | #Copy the /public directory for nginx 18 | #cp -r public/* ~/public 19 | 20 | #["/usr/sbin/sshd", "-D", "-p", "4100"] 21 | /usr/sbin/sshd -D -p 3100 & 22 | 23 | #Launch KeystoneJS and ConnextCMS 24 | exec "$@" 25 | -------------------------------------------------------------------------------- /client/vm/simple/output-files/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "connect-client.js", 3 | "version": "1.0.0", 4 | "description": "Connects the Client to the P2P VPS server", 5 | "main": "connectClient.js", 6 | "dependencies": { 7 | "execa": "^0.8.0", 8 | "express": "^4.16.2", 9 | "express3-handlebars": "^0.5.2", 10 | "node-promise": "^0.5.12", 11 | "request": "^2.83.0", 12 | "reverse-tunnel-ssh": "^1.1.0", 13 | "winston": "^2.4.0" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/RPiOVN/p2pvps-client" 18 | }, 19 | "devDependencies": {}, 20 | "scripts": { 21 | "test": "echo \"Error: no test specified\" && exit 1" 22 | }, 23 | "author": "chris.troutner@gmail.com", 24 | "license": "MIT" 25 | } 26 | -------------------------------------------------------------------------------- /client/vm/simple/p2p-vps-client.js: -------------------------------------------------------------------------------- 1 | /* 2 | This is the primary 'governor' application that drives a Client device and allows it to communicate 3 | with a P2P VPS Server. The scope of this application covers: 4 | 5 | * It reads the device-config.json file and registers the Client device with the P2P VPS server. 6 | 7 | * It builds the Docker container with information returned by the server after registration. 8 | 9 | * It launches the Docker container after being built. 10 | 11 | * It sends a heartbeat signal to the P2P VPS server every 10 minutes. The server responds with an 12 | expiration date. 13 | * (Maybe I can also send benchmark data to the server?) 14 | 15 | * When the expiration date is reached, or the Server can not be reached after 30 minutes, the governor 16 | software stops the Docker container and wipes the flash drive. It then reregisters itself with the 17 | P2P VPS marketplace. 18 | 19 | * If the Client can not make contact with the Server, it quietly retries to make contact every 2 minutes. 20 | 21 | Specifications for this program can be found here: 22 | https://github.com/P2PVPS/p2pvps-server/blob/master/specifications/client-specification.md 23 | */ 24 | 25 | /* 26 | * Copyright 2017 Chris Troutner & P2PVPS.org 27 | * MIT License. See LICENSE.md for details. 28 | */ 29 | 30 | //This file registers with the server 31 | "use strict"; 32 | 33 | // Express Dependencies 34 | const express = require("express"); 35 | const execa = require("execa"); 36 | 37 | // Global Variables 38 | const app = express(); 39 | const port = 4000; 40 | 41 | // Read in device-config.json file 42 | let deviceConfig; 43 | try { 44 | deviceConfig = require("./device-config.json"); 45 | console.log(`Registering device ID ${deviceConfig.deviceId}`); 46 | } catch (err) { 47 | const msg = "Could not open the device-config.json file! Exiting."; 48 | console.error(msg, err); 49 | process.exit(1); 50 | } 51 | 52 | // Each type of client shell will have a unique write-files.js library. 53 | const WriteFiles = require("./lib/write-files.js"); 54 | const writeFiles = new WriteFiles(deviceConfig); 55 | 56 | // Initialize the debugging logger. 57 | const Logger = require("../../lib/logger.js"); 58 | const logr = new Logger(deviceConfig); 59 | 60 | // Utility functions for dealing with the P2P VPS server. Shared by all clients. 61 | const P2pVpsServer = require("../../lib/p2p-vps-server.js"); 62 | const p2pVpsServer = new P2pVpsServer(deviceConfig, logr); 63 | 64 | // Create an Express server. Future development will allow serving of webpages and creation of Client API. 65 | const ExpressServer = require("../../lib/express-server.js"); 66 | const expressServer = new ExpressServer(app, port); 67 | expressServer.start(); 68 | 69 | // This is a high-level function used to register this Client with the Server. 70 | // It calls the registration function, writes out the support files, builds the Docker container, 71 | // and launches the Docker container. 72 | function registerDevice() { 73 | logr.debug(`Registering device ${deviceConfig.deviceId}`); 74 | 75 | //Simulate benchmark tests with dummy data. 76 | const now = new Date(); 77 | const deviceSpecs = { 78 | memory: "Fake Test Data", 79 | diskSpace: "Fake Test Data", 80 | processor: "Fake Test Data", 81 | internetSpeed: "Fake Test Data", 82 | checkinTimeStamp: now.toISOString(), 83 | }; 84 | 85 | const config = { 86 | deviceId: deviceConfig.deviceId, 87 | deviceSpecs: deviceSpecs, 88 | }; 89 | 90 | const execaOptions = { 91 | stdout: "inherit", 92 | stderr: "inherit", 93 | }; 94 | 95 | // Register with the server. 96 | p2pVpsServer 97 | .register(config) 98 | 99 | // Write out support files (Dockerfile, config.json) 100 | .then(clientData => { 101 | //debugger; 102 | //console.log(`clientData: ${JSON.stringify(clientData, null, 2)}`); 103 | // Save data to a global variable for use in later functions. 104 | global.clientData = clientData.device; 105 | 106 | return ( 107 | // Write out the Dockerfile. 108 | writeFiles 109 | .writeDockerfile( 110 | clientData.device.port, 111 | clientData.device.username, 112 | clientData.device.password 113 | ) 114 | 115 | // Write out the config file. 116 | .then(() => { 117 | return writeFiles.writeClientConfig(); 118 | }) 119 | 120 | .catch(err => { 121 | logr.error("Problem writing out support files: ", err); 122 | }) 123 | ); 124 | }) 125 | 126 | // Build the Docker container. 127 | .then(() => { 128 | logr.log("Building Docker Image."); 129 | 130 | return execa("./lib/buildImage", undefined, execaOptions) 131 | .then(result => { 132 | //debugger; 133 | console.log(result.stdout); 134 | }) 135 | .catch(err => { 136 | debugger; 137 | console.error("Error while trying to build Docker image!", err); 138 | logr.error("Error while trying to build Docker image!", err); 139 | logr.error(JSON.stringify(err, null, 2)); 140 | process.exit(1); 141 | }); 142 | }) 143 | 144 | // Run the Docker container 145 | .then(() => { 146 | logr.log("Running the Docker image."); 147 | 148 | return execa("./lib/runImage", undefined, execaOptions) 149 | .then(result => { 150 | //debugger; 151 | logr.log(result.stdout); 152 | }) 153 | .catch(err => { 154 | debugger; 155 | logr.error("Error while trying to run Docker image!"); 156 | logr.error(JSON.stringify(err, null, 2)); 157 | process.exit(1); 158 | }); 159 | }) 160 | 161 | .then(() => { 162 | logr.log("Docker image has been built and is running."); 163 | 164 | // Begin timer to check expiration. 165 | p2pVpsServer.startExpirationTimer(registerDevice); 166 | }) 167 | 168 | .catch(err => { 169 | logr.error("Error in main program: ", err); 170 | process.exit(1); 171 | }); 172 | } 173 | registerDevice(); 174 | -------------------------------------------------------------------------------- /images/federated-diagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/P2PVPS/p2pvps-client/d424e89c5497d6039cef0a03d02ed0df260d28a0/images/federated-diagram.jpg -------------------------------------------------------------------------------- /images/flash-client.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/P2PVPS/p2pvps-client/d424e89c5497d6039cef0a03d02ed0df260d28a0/images/flash-client.jpg -------------------------------------------------------------------------------- /images/marketplace-mockup.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/P2PVPS/p2pvps-client/d424e89c5497d6039cef0a03d02ed0df260d28a0/images/marketplace-mockup.JPG -------------------------------------------------------------------------------- /images/owned-devices-mockup.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/P2PVPS/p2pvps-client/d424e89c5497d6039cef0a03d02ed0df260d28a0/images/owned-devices-mockup.JPG -------------------------------------------------------------------------------- /images/rental-mockup.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/P2PVPS/p2pvps-client/d424e89c5497d6039cef0a03d02ed0df260d28a0/images/rental-mockup.JPG -------------------------------------------------------------------------------- /images/simple-diagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/P2PVPS/p2pvps-client/d424e89c5497d6039cef0a03d02ed0df260d28a0/images/simple-diagram.jpg -------------------------------------------------------------------------------- /images/temp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/P2PVPS/p2pvps-client/d424e89c5497d6039cef0a03d02ed0df260d28a0/images/temp.jpg -------------------------------------------------------------------------------- /images/testimage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/P2PVPS/p2pvps-client/d424e89c5497d6039cef0a03d02ed0df260d28a0/images/testimage.jpg -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "p2pvps-client", 3 | "version": "1.0.1", 4 | "description": "This is Client software for P2P VPS marketplace.", 5 | "main": "index.js", 6 | "private": "false", 7 | "scripts": { 8 | "test": "echo no tests yet." 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/P2PVPS/p2pvps-client" 13 | }, 14 | "author": "Chris Troutner", 15 | "license": "MIT", 16 | "homepage": "http://p2pvps.org", 17 | "devDependencies": { 18 | "babel-cli": "^6.26.0", 19 | "babel-preset-env": "^1.6.1", 20 | "eslint": "^4.11.0", 21 | "eslint-plugin-prettier": "^2.3.1", 22 | "eslint-plugin-promise": "^3.6.0", 23 | "eslint-plugin-unicorn": "^3.0.0", 24 | "prettier": "^1.8.2", 25 | "winston": "^2.4.0", 26 | "execa": "^0.8.0", 27 | "get-stream": "^3.0.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /support/Encrypting-Docker-containers-on-a-Virtual-Server.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/P2PVPS/p2pvps-client/d424e89c5497d6039cef0a03d02ed0df260d28a0/support/Encrypting-Docker-containers-on-a-Virtual-Server.pdf --------------------------------------------------------------------------------