├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin.js ├── index.js ├── package.json ├── public ├── index.html ├── qr.html └── style.css └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stop-server [![](https://badge.fury.io/js/stop-server.svg)](https://www.npmjs.com/package/stop-server) [![](https://travis-ci.org/typicode/stop-server.svg?branch=master)](https://travis-ci.org/typicode/stop-server) 2 | 3 | > Shut down your computer with your phone :iphone: (works on OS X, Linux and Windows) 4 | 5 | ## Install 6 | 7 | ```sh 8 | npm install -g stop-server 9 | ``` 10 | 11 | ```sh 12 | stop-server start # Need to be done only once 13 | ``` 14 | 15 | ## Usage 16 | 17 | Visit [`http://your-local-ip:5709`](http://localhost:5709/qr.html) __on your phone__. You should see this page: 18 | 19 | ![](http://i.imgur.com/4WadpZc.png) 20 | 21 | __Important__ if you're on OS X or Linux, you need to allow commands to be used without sudo: 22 | 23 | ```bash 24 | # Run 'sudo visudo' and add 25 | your-username ALL=NOPASSWD: /sbin/shutdown # OS X and Linux 26 | your-username ALL=NOPASSWD: /usr/sbin/pm-suspend # Linux only 27 | ``` 28 | 29 | __Tip__ for easier access, you can get a QR code by going to [http://localhost:5709/qr.html](http://localhost:5709/qr.html) from your computer 30 | 31 | ## Uninstall 32 | 33 | ``` 34 | npm rm -g stop-server 35 | ``` 36 | 37 | ## How it works? 38 | 39 | `stop-server` is a simple Express server with only 2 routes: 40 | 41 | ``` 42 | POST http://your-local-ip:5709/power-off 43 | POST http://your-local-ip:5709/sleep 44 | ``` 45 | 46 | ## License 47 | 48 | MIT - [typicode :cactus:](https://github.com/typicode/stop-server) 49 | -------------------------------------------------------------------------------- /bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var os = require('os') 3 | var path = require('path') 4 | var updateNotifier = require('update-notifier') 5 | var sudoBlock = require('sudo-block') 6 | var startup = require('user-startup') 7 | var chalk = require('chalk') 8 | var got = require('got') 9 | var address = require('network-address') 10 | var pkg = require('./package.json') 11 | 12 | sudoBlock('Should not be run as root, please retry without sudo.') 13 | 14 | updateNotifier({pkg: pkg}).notify() 15 | 16 | function stop () { 17 | startup.remove('stop-server') 18 | got.delete('localhost:' + 5709) 19 | } 20 | 21 | function start () { 22 | var log = path.join(os.tmpdir(), 'stop-server') 23 | startup.create('stop-server', process.execPath, [__dirname], log) 24 | 25 | if (os.platform() !== 'win32') { 26 | console.log( 27 | [ 28 | '', 29 | '---', 30 | 'To complete installation, you need to allow \'shutdown\' to be run without sudo.', 31 | 'Please run ' + chalk.cyan('sudo visudo') + ' and add ' + chalk.cyan('your-username ALL=NOPASSWD: /sbin/shutdown'), 32 | '---' 33 | ].join('\n') 34 | ) 35 | } 36 | 37 | console.log( 38 | [ 39 | '', 40 | 'To access stop-server from your phone, scan the QR code here', 41 | chalk.cyan('http://localhost:5709/qr.html'), 42 | '', 43 | 'Or go directly to', 44 | chalk.cyan('http://' + address() + ':5709'), 45 | '' 46 | ].join('\n') 47 | ) 48 | } 49 | 50 | var yargs = require('yargs') 51 | .version(pkg.version) 52 | .alias('v', 'version') 53 | .usage('Usage: $0 start|stop') 54 | .demand(1) 55 | 56 | var argv = yargs.argv 57 | 58 | if (argv._[0] === 'start') return start() 59 | if (argv._[0] === 'stop') return stop() 60 | 61 | console.log(yargs.showHelp) 62 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var util = require('util') 3 | var express = require('express') 4 | var address = require('network-address') 5 | var updateNotifier = require('update-notifier') 6 | var powerOff = require('power-off') 7 | var sleepMode = require('sleep-mode') 8 | var pkg = require('./package.json') 9 | 10 | var app = express() 11 | var notifier = updateNotifier({ pkg: pkg }) 12 | 13 | app.use(express.static(path.join(__dirname, 'public'))) 14 | 15 | app.delete('/', function (req, res) { 16 | res.end() 17 | util.log('exit') 18 | process.exit() 19 | }) 20 | 21 | app.post('/power-off', function (req, res) { 22 | powerOff(function (err, stderr, stdout) { 23 | if (err) { 24 | util.log(err) 25 | res.status(500).json({ error: 'Can\'t run power-off' }) 26 | } else { 27 | res.end() 28 | } 29 | }) 30 | }) 31 | 32 | app.post('/sleep', function (req, res) { 33 | sleepMode(function (err, stderr, stdout) { 34 | if (err) { 35 | util.log(err) 36 | res.status(500).json({ error: 'Can\'t run sleep' }) 37 | } else { 38 | res.end() 39 | } 40 | }) 41 | }) 42 | 43 | app.get('/address', function (req, res) { 44 | res.json({ address: address() }) 45 | }) 46 | 47 | app.get('/update', function (req, res) { 48 | updateNotifier({ 49 | pkg: pkg, 50 | callback: function (err, update) { 51 | if (err) return res.json({}) 52 | res.json(update) 53 | } 54 | }) 55 | }) 56 | 57 | var port = 5709 58 | 59 | app.listen(port, function () { 60 | util.log('stop-server listening on port ' + port) 61 | }) 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stop-server", 3 | "version": "0.3.0", 4 | "description": "Stop your computer using your phone or your tablet, from your bed or your couch", 5 | "main": "index.js", 6 | "bin": "bin.js", 7 | "scripts": { 8 | "test": "node test", 9 | "start": "node index", 10 | "uninstall": "node bin stop", 11 | "prepublish": "pkg-ok" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/typicode/stop-server.git" 16 | }, 17 | "keywords": [ 18 | "stop", 19 | "shutdown", 20 | "halt", 21 | "poweroff", 22 | "sleep", 23 | "server" 24 | ], 25 | "author": "typicode ", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/typicode/stop-server/issues" 29 | }, 30 | "homepage": "https://github.com/typicode/stop-server#readme", 31 | "dependencies": { 32 | "chalk": "^1.1.1", 33 | "express": "^4.13.3", 34 | "got": "^4.1.1", 35 | "network-address": "^1.0.0", 36 | "pkg-ok": "^1.0.1", 37 | "power-off": "^1.0.0", 38 | "sleep-mode": "^1.1.0", 39 | "sudo-block": "^1.2.0", 40 | "update-notifier": "^0.5.0", 41 | "user-startup": "^0.1.0", 42 | "yargs": "^3.21.0" 43 | }, 44 | "devDependencies": { 45 | "supertest": "^1.0.1" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | stop-server 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | Power Off 15 |
16 |
17 | Sleep 18 |
19 | 20 | 23 |
24 | 25 | 26 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /public/qr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | stop-server 6 | 7 | 8 | 9 | 10 |
11 |

12 |

13 |

14 |

15 | Scan this QR code or go to
16 |
17 | on your phone or tablet. 18 |

19 |
20 | 21 | 22 | 23 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /public/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 10px; 3 | text-align: center; 4 | } 5 | 6 | .btn { 7 | display: block; 8 | } 9 | 10 | .btn:first-of-type { 11 | margin-top: 10px; 12 | margin-bottom: 20px; 13 | } 14 | 15 | footer a { 16 | color: #777 !important 17 | } 18 | 19 | /* http://mystrd.at/modern-clean-css-sticky-footer/ */ 20 | html { 21 | position: relative; 22 | min-height: 100%; 23 | } 24 | body { 25 | margin-bottom: 40px; /* bottom = footer height */ 26 | } 27 | footer { 28 | position: absolute; 29 | left: 0; 30 | bottom: 0; 31 | height: 40px; 32 | width: 100%; 33 | } 34 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var request = require('supertest') 2 | var server = require('./') 3 | 4 | request('http://localhost:5709').get('/').expect(200, function(err){ 5 | if (err) throw err 6 | process.exit() 7 | }) 8 | --------------------------------------------------------------------------------