minor cleanup
[libremanage.git] / libremanage
CommitLineData
bbc21163
AR
1#!/usr/bin/python3
2
3f7ab73a
AR
3"""
4libremanage - Lightweight, free software for remote side-chanel server management
5
6Copyright (C) 2018 Alyssa Rosenzweig
7
8This program is free software: you can redistribute it and/or modify
9it under the terms of the GNU Affero General Public License as published by
10the Free Software Foundation, either version 3 of the License, or
11(at your option) any later version.
12
13This program is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16GNU Affero General Public License for more details.
17
18You should have received a copy of the GNU Affero General Public License
19along with this program. If not, see <https://www.gnu.org/licenses/>.
20"""
93f19e92 21
b266c229 22USAGE = """
93f19e92
AR
23Usage:
24
25 $ libremanage [server name] [command]
26
27Example:
28
29 $ libremanage web2 reboot
30
31Server names are defined in the accompanying config.py.
32
33Valid commands are as follows:
34
35 - shutdown, reboot, poweron: Power management
9ed4bf16 36 - tty: Open TTY in GNU Screen
c5f054a2 37 - sanity, sanity-sh: SSH sanity tests, which will whoami or just run sh.
60c3f92a
AR
38
39Define a configuration file in ~/.libremanage.json. See the included
40config.json for an example. Servers correspond to managed servers; managers
41correspond to single-board computers connecting the servers. libremanage SSHs
42into the manager to access the server through the side-channel.
93f19e92
AR
43"""
44
22f864bb
AR
45import sys
46import json
47import functools
48import subprocess
21ead159 49import time
60c3f92a 50import os.path
e14c36b3 51
2b7bbcf2 52def open_ssh(server, command, force_tty=False):
2f537a8d 53 print(command)
28c204f9 54 config = server["ssh"]
60c3f92a
AR
55 args = ["ssh"] + (["-t"] if force_tty else []) + [config["username"] + "@" + config["host"], "-p", str(config["port"]), command]
56 subprocess.run(args)
e14c36b3 57
40a688c0
AR
58def die_with_usage(message):
59 print(message)
b266c229
AR
60 print(USAGE)
61 sys.exit(1)
62
d8ddde1a 63def get_server_handle(name):
9537b802
AR
64 if name == "localhost":
65 return
66
38e0ee81
AR
67 try:
68 server = CONFIG["servers"][name]
69 except KeyError:
70 die_with_usage("Unknown server, please configure")
71
72 # Associate manager configuration
82ee0c31 73 server["ssh"] = CONFIG["managers"][server["manager"]]
38e0ee81 74
6e59af0c
AR
75 # Meta access
76 server["name"] = name
77
82ee0c31 78 return server
d8ddde1a 79
2f537a8d
AR
80def power_write(server, conf, state):
81 if conf["type"] == "hidusb-relay-cmd":
82 verb = "on" if state == 1 else "off"
83 open_ssh(server, "hidusb-relay-cmd ID=" + conf["relay"] + " " + verb + " " + str(conf["channel"]))
21ead159 84 else:
2f537a8d 85 print("Unknown power type " + conf["type"])
21ead159 86
2f537a8d 87def power_button(server, conf, state):
21ead159
AR
88 # Hold down the power to force off (via the EC),
89 # or just flick on to turn on
90
2f537a8d
AR
91 power_write(server, conf, 1)
92 time.sleep(6 if state == POWER_OFF else 1)
93 power_write(server, conf, 0)
21ead159
AR
94
95POWER_OFF = 0
96POWER_ON = 1
97POWER_REBOOT = 2
98
d8ddde1a 99def set_server_power(state, server):
e799dcd1
AR
100 conf = server["power"]
101
21ead159
AR
102 # Act like a power button
103
104 if state == POWER_OFF or state == POWER_ON:
2f537a8d 105 power_button(server, conf, state)
21ead159
AR
106 elif state == POWER_REBOOT:
107 # Requires that we already be online.
2f537a8d
AR
108 power_button(server, conf, POWER_OFF)
109 power_button(server, conf, POWER_ON)
d8ddde1a 110
6e59af0c
AR
111def open_tty(s):
112 if s["tty"]["uncolor"]:
113 # Broken serial port, workaround TTY garbage with libremanage-serial
85edc121 114 subprocess.run(["libremanage-serial", s["name"]])
6e59af0c
AR
115 else:
116 # Use native GNU screen
117 return open_ssh(s, "screen " + s["tty"]["file"] + " " + str(s["tty"]["baud"]), force_tty=True),
118
d8ddde1a 119COMMANDS = {
e14d7650
AR
120 # Power managemment
121
21ead159
AR
122 "shutdown": functools.partial(set_server_power, POWER_OFF),
123 "poweron": functools.partial(set_server_power, POWER_ON),
124 "reboot": functools.partial(set_server_power, POWER_REBOOT),
e14d7650
AR
125
126 # TTY access (or keyboard if wired as such)
127
6e59af0c 128 "tty": open_tty,
8a60d903 129 "tty-baud": lambda s: open_ssh(s, "stty -F "+ s["tty"]["file"] + " cs7 cstopb -ixon raw speed " + str(s["tty"]["baud"])),
bc9c5ec0 130 "tty-read": lambda s: open_ssh(s, "cat " + s["tty"]["file"], force_tty=True),
8b1252ae 131 "tty-write": lambda s: open_ssh(s, "stdbuf -o0 cat > " + s["tty"]["file"], force_tty=True),
e14c36b3 132
da2eb513 133 # SSH sanity tests
82ee0c31 134
28c204f9 135 "sanity": lambda s: open_ssh(s, "whoami"),
a69e9151 136 "sanity-sh": lambda s: open_ssh(s, ""),
d8ddde1a
AR
137}
138
d24528e4 139def issue_command(server_name, command):
d8ddde1a 140 server = get_server_handle(server_name)
40a688c0
AR
141
142 try:
da2eb513 143 callback = COMMANDS[command]
40a688c0
AR
144 except KeyError:
145 die_with_usage("Invalid command supplied")
d24528e4 146
82ee0c31 147 callback(server)
e14d7650 148
22f864bb
AR
149# Load configuration, get command, and go!
150
60c3f92a
AR
151try:
152 with open(os.path.expanduser("~/.libremanage.json")) as f:
153 CONFIG = json.load(f)
154except FileNotFoundError:
155 die_with_usage("Configuration file missing in ~/.libremanage.json")
22f864bb
AR
156
157if len(sys.argv) != 3:
158 die_with_usage("Incorrect number of arguments")
159
d24528e4 160issue_command(sys.argv[1], sys.argv[2])