- shutdown, reboot, poweron: Power management
- tty: Open TTY in GNU Screen
+ - sanity, sanity-sh: SSH sanity tests, ignore
+
+Define a configuration file in ~/.libremanage.json. See the included
+config.json for an example. Servers correspond to managed servers; managers
+correspond to single-board computers connecting the servers. libremanage SSHs
+into the manager to access the server through the side-channel.
"""
import sys
import json
import functools
import subprocess
+import time
+import os.path
def open_ssh(server, command, force_tty=False):
config = server["ssh"]
- subprocess.run(["ssh", "-t" if force_tty else "", config["username"] + "@" + config["host"], "-p", str(config["port"]), command])
+ args = ["ssh"] + (["-t"] if force_tty else []) + [config["username"] + "@" + config["host"], "-p", str(config["port"]), command]
+ subprocess.run(args)
def die_with_usage(message):
print(message)
sys.exit(1)
def get_server_handle(name):
+ if name == "localhost":
+ return
+
try:
server = CONFIG["servers"][name]
except KeyError:
# Associate manager configuration
server["ssh"] = CONFIG["managers"][server["manager"]]
+ # Meta access
+ server["name"] = name
+
return server
+def gpio_export(server, pin, mode):
+ if mode:
+ open_ssh(server, "echo " + str(pin) + " > /sys/class/gpio/export")
+ open_ssh(server, "echo out > /sys/class/gpio/gpio" + str(pin) + "/direction")
+ else:
+ open_ssh(server, "echo " + str(pin) + " > /sys/class/gpio/unexport")
+
+def gpio_write(server, pin, value):
+ open_ssh(server, "echo " + str(value) + " > /sys/class/gpio/gpio" + str(pin) + "/value")
+
+def power_button(server, pin, state):
+ # Hold down the power to force off (via the EC),
+ # or just flick on to turn on
+
+ gpio_write(server, pin, 1)
+ time.sleep(2 if state == POWER_OFF else 0.5)
+ gpio_write(server, pin, 0)
+
+POWER_OFF = 0
+POWER_ON = 1
+POWER_REBOOT = 2
+
def set_server_power(state, server):
conf = server["power"]
- # Set invert to write LOW for power on and HIGH for off
- if conf["invert"]:
- state = 1 - state
-
+ # TODO: Invert
# Export pin, configure, write value, unexport
- open_ssh(server, "echo " + str(conf["pin"]) + " > /sys/class/gpio/export")
- open_ssh(server, "echo out > /sys/class/gpio/gpio" + str(conf["pin"]) + "/direction")
- open_ssh(server, "echo " + str(state) + " > /sys/class/gpio/gpio" + str(conf["pin"]) + "/value")
- open_ssh(server, "echo " + str(conf["pin"]) + " > /sys/class/gpio/unexport")
+ pin = conf["pin"]
+
+ gpio_export(server, pin, True)
+
+ # Act like a power button
+
+ if state == POWER_OFF or state == POWER_ON:
+ power_button(server, pin, state)
+ elif state == POWER_REBOOT:
+ # Requires that we already be online.
+ power_button(server, pin, POWER_OFF)
+ power_button(server, pin, POWER_ON)
+
+ gpio_export(server, pin, False)
+
+def open_tty(s):
+ if s["tty"]["uncolor"]:
+ # Broken serial port, workaround TTY garbage with libremanage-serial
+ subprocess.run(["libremanage-serial", s["name"]])
+ else:
+ # Use native GNU screen
+ return open_ssh(s, "screen " + s["tty"]["file"] + " " + str(s["tty"]["baud"]), force_tty=True),
COMMANDS = {
# Power managemment
- "shutdown": functools.partial(set_server_power, 0),
- "poweron": functools.partial(set_server_power, 1),
- "reboot": lambda s: (set_server_power(0, s), set_server_power(1, s)),
+ "shutdown": functools.partial(set_server_power, POWER_OFF),
+ "poweron": functools.partial(set_server_power, POWER_ON),
+ "reboot": functools.partial(set_server_power, POWER_REBOOT),
# TTY access (or keyboard if wired as such)
- "tty": lambda s: open_ssh(s, "screen " + s["tty"]["file"] + " " + str(s["tty"]["baud"]), force_tty=True),
+ "tty": open_tty,
+ "tty-baud": lambda s: open_ssh(s, "stty -F "+ s["tty"]["file"] + " " + str(s["tty"]["baud"])),
+ "tty-read": lambda s: open_ssh(s, "cat " + s["tty"]["file"], force_tty=True),
+ "tty-write": lambda s: open_ssh(s, "stdbuf -o0 cat > " + s["tty"]["file"], force_tty=True),
# SSH sanity tests
"sanity": lambda s: open_ssh(s, "whoami"),
- "console": lambda s: open_ssh(s, ""),
+ "sanity-sh": lambda s: open_ssh(s, ""),
}
def issue_command(server_name, command):
# Load configuration, get command, and go!
-with open("config.json") as f:
- CONFIG = json.load(f)
+try:
+ with open(os.path.expanduser("~/.libremanage.json")) as f:
+ CONFIG = json.load(f)
+except FileNotFoundError:
+ die_with_usage("Configuration file missing in ~/.libremanage.json")
if len(sys.argv) != 3:
die_with_usage("Incorrect number of arguments")