Add hidusb relay instructions
[libremanage.git] / libremanage
index d274d80a7373b1502ecc2e8badb2cf1151ae7bb9..8776159b1539f039133ea8d0823a9748676b74d6 100755 (executable)
@@ -34,16 +34,25 @@ Valid commands are as follows:
 
     - 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)
@@ -51,6 +60,9 @@ def die_with_usage(message):
     sys.exit(1)
 
 def get_server_handle(name):
+    if name == "localhost":
+        return
+
     try:
         server = CONFIG["servers"][name]
     except KeyError:
@@ -59,36 +71,79 @@ def get_server_handle(name):
     # 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):
@@ -103,8 +158,11 @@ 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")