123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- #!/usr/bin/python3
- """
- libremanage - Lightweight, free software for remote side-chanel server management
- Copyright (C) 2018 Alyssa Rosenzweig <alyssa@rosenzweig.io>
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
- """
- USAGE = """
- Usage:
- $ libremanage [server name] [command]
- Example:
- $ libremanage web2 reboot
- Valid commands are as follows:
- - shutdown, reboot, poweron: Power management
- - tty: Open TTY in GNU Screen
- - sanity: Sanity test to check if manager is reachable
- - sanity-sh: Sanity test exposing a shell on the manager
- Define a configuration file in ~/.libremanage.json. See the included
- config.json for an example. Named 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):
- cfg = server["ssh"]
- args = ["ssh"] + (["-t"] if force_tty else []) + [cfg["username"] + "@" + cfg["host"], "-p", str(cfg["port"]), command]
- subprocess.run(args)
- def die_with_usage(message):
- print(message)
- print(USAGE)
- sys.exit(1)
- def get_server_handle(name):
- try:
- server = CONFIG["servers"][name]
- except KeyError:
- die_with_usage("Unknown server, please configure")
- # Associate manager configuration
- server["ssh"] = CONFIG["managers"][server["manager"]]
- return server
- """
- Power management: currently, we only support the `hidusb-relay-cmd` driver,
- wired up as to the power button pins. We may want to expose more options in the
- config for other boards.
- """
- POWER_OFF = 0
- POWER_ON = 1
- POWER_REBOOT = 2
- def set_server_power(state, server):
- conf = server["power"]
- # Ensure the button is in a known state
- power_write(server, conf, 0)
- if state == POWER_OFF or state == POWER_ON:
- power_button(server, conf, state)
- elif state == POWER_REBOOT:
- # Requires that we already be online.
- power_button(server, conf, POWER_OFF)
- power_button(server, conf, POWER_ON)
- def power_write(server, conf, state):
- if conf["type"] == "hidusb-relay-cmd":
- verb = "on" if state == 1 else "off"
- open_ssh(server, "hidusb-relay-cmd ID=" + conf["relay"] + " " + verb + " " + str(conf["channel"]))
- else:
- die_with_usage("Unknown power type " + conf["type"])
- def power_button(server, conf, state):
- # Hold down the power to force off (via the EC),
- # or just flick on to turn on
- power_write(server, conf, 1)
- time.sleep(conf["timing"]["off" if state == POWER_OFF else "on"])
- power_write(server, conf, 0)
- """
- Define the list of commands implemented as a dict mapping names to functions
- actuating the command
- """
- COMMANDS = {
- # Power managemment
- "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),
- # SSH sanity tests
- "sanity": lambda s: open_ssh(s, "whoami"),
- "sanity-sh": lambda s: open_ssh(s, ""),
- }
- def issue_command(server_name, command):
- server = get_server_handle(server_name)
- try:
- callback = COMMANDS[command]
- except KeyError:
- die_with_usage("Invalid command supplied")
- callback(server)
- # Load configuration, get command, and go!
- 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")
- issue_command(sys.argv[1], sys.argv[2])
|