netboot: script to configure a remote host from toml and netboot a kernel
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
diff --git a/netboot/README.md b/netboot/README.md
new file mode 100644
index 0000000..717f156
--- /dev/null
+++ b/netboot/README.md
@@ -0,0 +1,28 @@
+Netboot a remote OpenBMC host via telnet (and hopefully at some stage, ssh).
+
+To configure, edit `${HOME}/.config/obmc-scripts/netboot` to configure a
+machine:
+
+```
+[foo]
+platform = "bar"
+user = "baz"
+password = "quux"
+
+ # telnet serial server
+ [foo.console]
+ host = "1.2.3.4"
+ port = 5678
+
+ [foo.u-boot]
+ commands = [
+ "setenv bootfile fitImage",
+ "tftp",
+ ]
+```
+
+Then netboot your machine:
+
+```
+$ ./netboot foo
+```
diff --git a/netboot/netboot b/netboot/netboot
new file mode 100755
index 0000000..3c941a0
--- /dev/null
+++ b/netboot/netboot
@@ -0,0 +1,96 @@
+#!/usr/bin/env python3
+
+import argparse
+import sys
+import time
+import toml
+from os import path
+from telnetlib import Telnet
+from types import MethodType
+from xdg import BaseDirectory
+
+def expect_or_raise(conn, patterns, timeout=None):
+ i, m, d = conn.expect([bytes(p, 'ascii') for p in patterns], timeout)
+ if i == -1:
+ msg = "Match failed, expected '%s', got '%s'" % (str(patterns), d)
+ print(msg, file=sys.stderr)
+ raise ValueError
+ return i, m, d
+
+def encode_and_write(conn, comm="", sep="\n"):
+ # Slow down the writes to help poor ol' serial-over-telnet
+ for c in comm + sep:
+ conn.write(bytes(c, 'ascii'))
+ time.sleep(0.01)
+
+def init_telnet(host, port=0, timeout=None):
+ conn = Telnet(host, port, timeout)
+ conn.encode_and_write = MethodType(encode_and_write, conn)
+ conn.expect_or_raise = MethodType(expect_or_raise, conn)
+ return conn
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("machine")
+ args = parser.parse_args()
+
+ confbase = BaseDirectory.save_config_path("obmc-scripts")
+ conffile = path.join(confbase, "netboot")
+ if not path.exists(conffile):
+ print("Missing configuration file: %s" % (conffile))
+ sys.exit(1)
+
+ conf = toml.load(conffile)
+ mach = conf[args.machine]
+ console = mach["console"]
+ conn = init_telnet(console["host"], console["port"])
+
+ try:
+ conn.encode_and_write()
+ i, m, d = conn.expect_or_raise([
+ "%s login:" % (mach["platform"]),
+ "root@%s:.*#" % (mach["platform"]),
+ "ast#",
+ ], 5)
+
+ if i != 2:
+ if i == 0:
+ conn.encode_and_write(mach["user"])
+ conn.read_until(b"Password:")
+ conn.encode_and_write(mach["password"])
+ conn.expect_or_raise(["root@%s:.*#" % (mach["platform"])])
+
+ conn.encode_and_write("reboot")
+
+ conn.expect_or_raise(["Hit any key to stop autoboot"])
+ conn.encode_and_write()
+
+ for comm in mach["u-boot"]["commands"]:
+ conn.encode_and_write(comm)
+ if "tftp" in comm:
+ i, m, d = conn.expect_or_raise([
+ r"Bytes transferred = \d+ \([0-9a-f]+ hex\)",
+ "Not retrying...",
+ r"[*]{3} ERROR:",
+ ])
+ if i == 1 or i == 2:
+ print("Error detected, exiting", file=sys.stderr)
+ return
+
+ conn.encode_and_write("printenv set_bootargs")
+ i, m, d = conn.expect_or_raise([
+ "set_bootargs=.*$",
+ "## Error: \"set_bootargs\" not defined",
+ ], 1)
+ if i == 0:
+ conn.encode_and_write("run set_bootargs")
+ conn.read_until(b"ast#")
+
+ conn.encode_and_write("bootm")
+ conn.read_until(b"Starting kernel")
+ finally:
+ conn.close()
+
+if __name__ == "__main__":
+ main()