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.
master
Dustin 2024-03-21 21:24:12 -05:00
parent 9779ac795d
commit d989994f25
9 changed files with 107 additions and 0 deletions

View File

@ -0,0 +1,9 @@
package schema
#SerTermSsh: {
authorized_keys: [...string]
}
#SerTerm: {
ssh: #SerTermSsh
}

41
app/serterm/templates.cue Normal file
View File

@ -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},
]
}
},
]

10
env/prod/serterm.cue vendored Normal file
View File

@ -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",
]

View File

@ -8,3 +8,5 @@ ssh: prod.ssh
sudo: prod.sudo sudo: prod.sudo
promtail: prod.#promtail promtail: prod.#promtail
serterm: prod.serterm

View File

@ -5,6 +5,7 @@ import (
"du5t1n.me/cfg/app/collectd" "du5t1n.me/cfg/app/collectd"
"du5t1n.me/cfg/app/promtail" "du5t1n.me/cfg/app/promtail"
"du5t1n.me/cfg/app/serterm"
"du5t1n.me/cfg/env/prod" "du5t1n.me/cfg/env/prod"
) )
@ -12,4 +13,5 @@ render: list.Concat([
prod.templates, prod.templates,
collectd.templates, collectd.templates,
promtail.templates, promtail.templates,
serterm.templates,
]) ])

View File

@ -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"

View File

@ -0,0 +1,3 @@
{% for key in serterm.ssh.authorized_keys -%}
{{ key }}
{% endfor -%}

View File

@ -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

View File

@ -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