4 libremanage - Lightweight, free software for remote side-chanel server management
6 Copyright (C) 2018 Alyssa Rosenzweig
8 This program is free software: you can redistribute it and/or modify
9 it under the terms of the GNU Affero General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU Affero General Public License for more details.
18 You should have received a copy of the GNU Affero General Public License
19 along with this program. If not, see <https://www.gnu.org/licenses/>.
25 $ libremanage [server name] [command]
29 $ libremanage web2 reboot
31 Server names are defined in the accompanying config.py.
33 Valid commands are as follows:
35 - shutdown, reboot, poweron: Power management
36 - tty: Open TTY in GNU Screen
37 - sanity, sanity-sh: SSH sanity tests, ignore
39 Define a configuration file in ~/.libremanage.json. See the included
40 config.json for an example. Servers correspond to managed servers; managers
41 correspond to single-board computers connecting the servers. libremanage SSHs
42 into the manager to access the server through the side-channel.
52 def open_ssh(server
, command
, force_tty
=False):
53 config
= server
["ssh"]
54 args
= ["ssh"] + (["-t"] if force_tty
else []) + [config
["username"] + "@" + config
["host"], "-p", str(config
["port"]), command
]
57 def die_with_usage(message
):
62 def get_server_handle(name
):
64 server
= CONFIG
["servers"][name
]
66 die_with_usage("Unknown server, please configure")
68 # Associate manager configuration
69 server
["ssh"] = CONFIG
["managers"][server
["manager"]]
73 def gpio_export(server
, pin
, mode
):
75 open_ssh(server
, "echo " + str(pin
) + " > /sys/class/gpio/export")
76 open_ssh(server
, "echo out > /sys/class/gpio/gpio" + str(pin
) + "/direction")
78 open_ssh(server
, "echo " + str(pin
) + " > /sys/class/gpio/unexport")
80 def gpio_write(server
, pin
, value
):
81 open_ssh(server
, "echo " + str(value
) + " > /sys/class/gpio/gpio" + str(pin
) + "/value")
83 def power_button(server
, pin
, state
):
84 # Hold down the power to force off (via the EC),
85 # or just flick on to turn on
87 gpio_write(server
, pin
, 1)
88 time
.sleep(2 if state
== POWER_OFF
else 0.5)
89 gpio_write(server
, pin
, 0)
95 def set_server_power(state
, server
):
96 conf
= server
["power"]
99 # Export pin, configure, write value, unexport
102 gpio_export(server
, pin
, True)
104 # Act like a power button
106 if state
== POWER_OFF
or state
== POWER_ON
:
107 power_button(server
, pin
, state
)
108 elif state
== POWER_REBOOT
:
109 # Requires that we already be online.
110 power_button(server
, pin
, POWER_OFF
)
111 power_button(server
, pin
, POWER_ON
)
113 gpio_export(server
, pin
, False)
118 "shutdown": functools
.partial(set_server_power
, POWER_OFF
),
119 "poweron": functools
.partial(set_server_power
, POWER_ON
),
120 "reboot": functools
.partial(set_server_power
, POWER_REBOOT
),
122 # TTY access (or keyboard if wired as such)
124 "tty": lambda s
: open_ssh(s
, "screen " + s
["tty"]["file"] + " " + str(s
["tty"]["baud"]), force_tty
=True),
125 "tty-baud": lambda s
: open_ssh(s
, "stty -F "+ s
["tty"]["file"] + " " + str(s
["tty"]["baud"])),
126 "tty-read": lambda s
: open_ssh(s
, "cat " + s
["tty"]["file"], force_tty
=True),
130 "sanity": lambda s
: open_ssh(s
, "whoami"),
131 "sanity-sh": lambda s
: open_ssh(s
, ""),
134 def issue_command(server_name
, command
):
135 server
= get_server_handle(server_name
)
138 callback
= COMMANDS
[command
]
140 die_with_usage("Invalid command supplied")
144 # Load configuration, get command, and go!
147 with
open(os
.path
.expanduser("~/.libremanage.json")) as f
:
148 CONFIG
= json
.load(f
)
149 except FileNotFoundError
:
150 die_with_usage("Configuration file missing in ~/.libremanage.json")
152 if len(sys
.argv
) != 3:
153 die_with_usage("Incorrect number of arguments")
155 issue_command(sys
.argv
[1], sys
.argv
[2])