Shutdown or reboot a remote server or pi from a dashboard button

One of the annoyances I find using a pi (particularly if configured as a remote headless server) is that it is not trivial to shut it down safely. This flow allows a server or pi to be shutdown or rebooted from a node-red flow running remotely (or locally) via a shutdown command sent via ssh from node-red. The flow triggers the action from a dashboard push-button and has an OK/Cancel popup to prevent against accidental actions.

There are a couple of things to do before importing the flow. Firstly, on the machine to be shutdown it is necessary that the shutdown command can be run without having to enter a password. You can test whether this is already the case by running, in a terminal sudo shutdown -r now which will either ask for a password (in which case you can Ctrl-C out of it) or it will reboot, in which case it is not necessary to do the next section.

If it did ask for a password then in a terminal run the command

sudo visudo

This will open an editor on the sudoers file. At the end add the line

pi ALL=(ALL) NOPASSWD: /sbin/shutdown

In the above line pi is the name of the user that will be used to connect via ssh, if using a different user name then enter that. Save the file and exit and now you should be able to reboot without entering a password.

Next it is necessary to be able to ssh into the machine without using a password. This is done using public key authentication. I am not going to cover this here, [1] should get you going if you don't know how to do this.

Now import the flow. It includes two example buttons, one which reboots the remote server and one which shuts it down. If you open one of the button configs it should be obvious what to put there to configure it for your server(s).

  • host is the hostname or ip address of the machine to be shutdown
  • user is the name of the user used to connect with ssh
  • description is an optional field that is displayed in the confirmation dialog
  • reboot is an optional field that specifies whether to reboot or shutdown. If it is present and true then a reboot is performed, otherwise a shutdown is performed

If you want buttons for multiple servers they can all be fed into the same subflow.

There is one small issue with the flow. When invoked it will show a 'command failed' error in the node-red log. This is because the ssh connection is broken by the shutdown. It can safely be ignored.

[1] https://www.linuxtrainingacademy.com/ssh-login-without-password/

[{"id":"496868c1.5496b8","type":"subflow","name":"Shutdown/Reboot","info":"","in":[{"x":53,"y":99,"wires":[{"id":"b1977423.bfb72"}]}],"out":[]},{"id":"c4d5a3aa.8911f","type":"ui_toast","z":"496868c1.5496b8","position":"dialog","displayTime":"3","highlight":"","outputs":1,"ok":"OK","cancel":"Cancel","topic":"","name":"confirm","x":422,"y":107,"wires":[["f05be770.20c318"]]},{"id":"b1977423.bfb72","type":"function","z":"496868c1.5496b8","name":"Prepare confirmation","func":"/* Given a payload containing \n * host: string containing host name or ip\n * user: user name to use to ssh into the host\n * description: optional string conatining description of host\n * reboot: optional true/false flag to indicate whether to reboot (true) or shutdown\n * defaults to shutdown\n * formats the data ready to be passed to notification node for OK/Cancel\n*/\nvar mode;       // mode for user display\nvar shMode;     // mode passed to shutdown\n\nif (msg.payload.reboot === true) {\n    mode = \"reboot\";\n    shMode = \"-r\";\n} else {\n    mode = \"shutdown\";\n    shMode = \"-h\";\n}\n// these will be passed through the notification node\nmsg.shMode = shMode;\nmsg.host = msg.payload.host;\nmsg.user = msg.payload.user;\n\nmsg.topic = \"Confirm \" + mode + \" \" + msg.payload.host;\nmsg.payload = msg.payload.description;    // may be undefined\nreturn msg;","outputs":1,"noerr":0,"x":227,"y":107,"wires":[["c4d5a3aa.8911f"]]},{"id":"f05be770.20c318","type":"switch","z":"496868c1.5496b8","name":"Ignore Cancel","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"OK","vt":"str"}],"checkall":"true","outputs":1,"x":589,"y":106,"wires":[["a130c70f.b2d588"]]},{"id":"a130c70f.b2d588","type":"change","z":"496868c1.5496b8","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"'ssh ' & user & '@' & host & \t\" 'sudo shutdown \" & shMode & \" now'\"","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":257,"y":188,"wires":[["e69fe87a.3dceb8"]]},{"id":"e69fe87a.3dceb8","type":"exec","z":"496868c1.5496b8","command":"","addpay":true,"append":"","useSpawn":"false","timer":"20","oldrc":false,"name":"Shutdown/Reboot","x":513,"y":188.5,"wires":[[],[],[]]},{"id":"1b46e0fd.bca717","type":"ui_button","z":"f0414dd0.dd514","name":"","group":"39fd61f4.9fecd6","order":0,"width":0,"height":0,"passthru":false,"label":"Reboot server","color":"","bgcolor":"","icon":"","payload":"{\"host\":\"192.168.xxx.xxx\",\"user\":\"username\",\"description\":\"server description\",\"reboot\":true}","payloadType":"json","topic":"","x":206,"y":645,"wires":[["73432365.8ec7fc"]]},{"id":"fd36fdb7.739dc8","type":"ui_button","z":"f0414dd0.dd514","name":"","group":"39fd61f4.9fecd6","order":0,"width":0,"height":0,"passthru":false,"label":"Shutdown server","color":"","bgcolor":"","icon":"","payload":"{\"host\":\"192.168.xxx.xxx\",\"user\":\"username\",\"description\":\"server description\",\"reboot\":false}","payloadType":"json","topic":"","x":212,"y":695,"wires":[["73432365.8ec7fc"]]},{"id":"73432365.8ec7fc","type":"subflow:496868c1.5496b8","z":"f0414dd0.dd514","x":470,"y":663,"wires":[]},{"id":"39fd61f4.9fecd6","type":"ui_group","z":"","name":"Default","tab":"b6c6f589.8e06a","disp":false,"width":"6"},{"id":"b6c6f589.8e06a","type":"ui_tab","z":"","name":"Test","icon":"dashboard","order":1}]
colinl

Flow Info

created 7 months, 2 weeks ago

Node Types

Core
  • change (x1)
  • exec (x1)
  • function (x1)
  • switch (x1)
Other
  • subflow (x1)
  • subflow:496868c1.5496b8 (x1)
  • ui_button (x2)
  • ui_group (x1)
  • ui_tab (x1)
  • ui_toast (x1)

Tags

  • shutdown
  • reboot
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option