From d989994f25d6a5bfa3a73bdddb789163025c6a00 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Thu, 21 Mar 2024 21:24:12 -0500 Subject: [PATCH] serterm: Deploy serial terminal server The serial terminal server ("serterm") is a collection of scripts that automate launching multiple `picocom` processes, one per USB-serial adapter connected to the system. Each `picocom` process has its own window in a `tmux` session, which is accessible via SSH on a dedicated port (20022). Clients connecting to that SSH server will be automatically attached to the `tmux` session, allowing them to access the serial terminal server quickly and easily. The SSH server only allows public-key authentication, so the authorized keys have to be pre-configured. In addition to automatically launching `picocom` windows for each serial port when the terminal server starts, ports that are added (hot-plugged) while the server is running will have windows created for them automatically, by way of a udev rule. Each `picocom` process is configured to log communications with its respective serial port. This may be useful, for example, to find diagnostic messages that may not be captured by the `tmux` scrollback buffer. --- app/serterm/schema/schema.cue | 9 ++++ app/serterm/templates.cue | 41 +++++++++++++++++++ env/prod/serterm.cue | 10 +++++ host/serial1.pyrocufflink.blue.cue | 2 + instructions/serial1.pyrocufflink.blue.cue | 2 + templates/serterm/95-serial-terminal.rules | 3 ++ templates/serterm/authorized_keys | 3 ++ .../serial-terminal-server-window@.service | 7 ++++ .../serterm/serial-terminal-server.container | 30 ++++++++++++++ 9 files changed, 107 insertions(+) create mode 100644 app/serterm/schema/schema.cue create mode 100644 app/serterm/templates.cue create mode 100644 env/prod/serterm.cue create mode 100644 templates/serterm/95-serial-terminal.rules create mode 100644 templates/serterm/authorized_keys create mode 100644 templates/serterm/serial-terminal-server-window@.service create mode 100644 templates/serterm/serial-terminal-server.container diff --git a/app/serterm/schema/schema.cue b/app/serterm/schema/schema.cue new file mode 100644 index 0000000..151ed59 --- /dev/null +++ b/app/serterm/schema/schema.cue @@ -0,0 +1,9 @@ +package schema + +#SerTermSsh: { + authorized_keys: [...string] +} + +#SerTerm: { + ssh: #SerTermSsh +} diff --git a/app/serterm/templates.cue b/app/serterm/templates.cue new file mode 100644 index 0000000..cd74e09 --- /dev/null +++ b/app/serterm/templates.cue @@ -0,0 +1,41 @@ +package serterm + +import "du5t1n.me/cfg/base/schema/instructions" + +templates: [...instructions.#RenderInstruction] & [ + { + template: "serterm/authorized_keys" + dest: "/etc/serterm/authorized_keys" + hooks: { + changed: [ + {run: "systemctl restart serial-terminal-server"}, + ] + } + }, + { + template: "serterm/95-serial-terminal.rules" + dest: "/etc/udev/rules.d/95-serial-terminal.rules" + hooks: { + changed: [{run: "udevadm control --reload"}] + } + }, + { + template: "serterm/serial-terminal-server.container" + dest: "/etc/containers/systemd/serial-terminal-server.container" + hooks: { + changed: [ + {run: "systemctl daemon-reload", immediate: true}, + {run: "systemctl restart serial-terminal-server"}, + ] + } + }, + { + template: "serterm/serial-terminal-server-window@.service" + dest: "/etc/systemd/system/serial-terminal-server-window@.service" + hooks: { + changed: [ + {run: "systemctl daemon-reload", immediate: true}, + ] + } + }, +] diff --git a/env/prod/serterm.cue b/env/prod/serterm.cue new file mode 100644 index 0000000..4ed45d1 --- /dev/null +++ b/env/prod/serterm.cue @@ -0,0 +1,10 @@ +package prod + +import "du5t1n.me/cfg/app/serterm/schema" + +serterm: schema.#SerTerm + +serterm: ssh: authorized_keys: [ + "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAINZCN2cxMDwedJ1Ke23Z3CZRcOYjqW8fFqsooRus7RK0AAAABHNzaDo= dustin@rosalina.pyrocufflink.blue", + "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIAB6xTCSNz+AcQCWcyVKs84tThXN4wpLgCo2Lc48L6EsAAAABHNzaDo= dustin@luma.pyrocufflink.blue", +] diff --git a/host/serial1.pyrocufflink.blue.cue b/host/serial1.pyrocufflink.blue.cue index 9801955..ce918a3 100644 --- a/host/serial1.pyrocufflink.blue.cue +++ b/host/serial1.pyrocufflink.blue.cue @@ -8,3 +8,5 @@ ssh: prod.ssh sudo: prod.sudo promtail: prod.#promtail + +serterm: prod.serterm diff --git a/instructions/serial1.pyrocufflink.blue.cue b/instructions/serial1.pyrocufflink.blue.cue index 5bbad0b..482d37b 100644 --- a/instructions/serial1.pyrocufflink.blue.cue +++ b/instructions/serial1.pyrocufflink.blue.cue @@ -5,6 +5,7 @@ import ( "du5t1n.me/cfg/app/collectd" "du5t1n.me/cfg/app/promtail" + "du5t1n.me/cfg/app/serterm" "du5t1n.me/cfg/env/prod" ) @@ -12,4 +13,5 @@ render: list.Concat([ prod.templates, collectd.templates, promtail.templates, + serterm.templates, ]) diff --git a/templates/serterm/95-serial-terminal.rules b/templates/serterm/95-serial-terminal.rules new file mode 100644 index 0000000..2cd38db --- /dev/null +++ b/templates/serterm/95-serial-terminal.rules @@ -0,0 +1,3 @@ +# udev worker process does not have permissions/capabilities to run +# `podman exec` directly, so a systemd unit is necessary +ACTION=="add", SUBSYSTEM=="tty", SUBSYSTEMS=="usb-serial", TAG+="systemd", ENV{SYSTEMD_WANTS}+="serial-terminal-server-window@%k.service" diff --git a/templates/serterm/authorized_keys b/templates/serterm/authorized_keys new file mode 100644 index 0000000..68772d1 --- /dev/null +++ b/templates/serterm/authorized_keys @@ -0,0 +1,3 @@ +{% for key in serterm.ssh.authorized_keys -%} +{{ key }} +{% endfor -%} diff --git a/templates/serterm/serial-terminal-server-window@.service b/templates/serterm/serial-terminal-server-window@.service new file mode 100644 index 0000000..1a9f716 --- /dev/null +++ b/templates/serterm/serial-terminal-server-window@.service @@ -0,0 +1,7 @@ +[Unit] +Description=Add serial terminal window for %I +After=serial-terminal-server.service + +[Service] +Type=oneshot +ExecStart=/usr/bin/podman exec serial-terminal-server add-window /dev/%I diff --git a/templates/serterm/serial-terminal-server.container b/templates/serterm/serial-terminal-server.container new file mode 100644 index 0000000..c48e3f6 --- /dev/null +++ b/templates/serterm/serial-terminal-server.container @@ -0,0 +1,30 @@ +[Unit] +After=network-online.target +Wants=network-online.target + +[Container] +ContainerName=serial-terminal-server +Image=git.pyrocufflink.net/containerimages/serial-terminal-server +Pull=newer +ReadOnly=true +VolatileTmp=true +Volume=serial-logs:/var/log/serial:rw,z,U +Volume=serial-ssh:/etc/ssh:rw,z,U +Volume=/dev:/dev:rw +Volume=/etc/serterm/authorized_keys:/run/serial/.ssh/authorized_keys:ro,z,U +PublishPort=20022:20022 +RunInit=true +# SELinux does not allow container_t access to devpts_t (for tmux) +SecurityLabelDisable=true +PodmanArgs=--device-cgroup-rule='c 188:* rw' +# This must be the GID of the "dialout" group on the host +# Using the group name would resolve the GID inside the container, +# which would not give the correct permissions. +PodmanArgs=--group-add=18 + +[Service] +Restart=always +RestartSec=2s + +[Install] +WantedBy=multi-user.target