#!/bin/sh DAEMON=/usr/bin/tera-seriald PIDFILE=/run/tera-seriald/tera-seriald.pid ENVFILE=/etc/tera/tera-seriald.env RUN_AS=tera:tera ensure_account() { if ! grep -q '^tera:' /etc/group; then addgroup -S tera || return 1 fi if ! id tera >/dev/null 2>&1; then adduser -S -D -H -s /bin/false -G tera tera || return 1 fi normalize_service_account_password || return 1 } normalize_service_account_password() { shadow_value="$(awk -F: '$1 == "tera" { print $2 }' /etc/shadow)" || return 1 case "$shadow_value" in ""|!*) ;; \**) ;; *) return 0 ;; esac command -v mkpasswd >/dev/null 2>&1 || return 1 random_secret="$(dd if=/dev/urandom bs=48 count=1 2>/dev/null | sha256sum | cut -d' ' -f1)" || return 1 password_hash="$(mkpasswd -m sha-256 "$random_secret")" || return 1 unset random_secret temporary_shadow="/etc/.shadow.tera.$$" trap 'rm -f "$temporary_shadow"' EXIT HUP INT TERM awk -F: -v OFS=: -v replacement="$password_hash" ' $1 == "tera" { $2 = replacement; found = 1 } { print } END { if (!found) exit 1 } ' /etc/shadow >"$temporary_shadow" || return 1 chown 0:0 "$temporary_shadow" || return 1 chmod 0600 "$temporary_shadow" || return 1 mv -f "$temporary_shadow" /etc/shadow || return 1 trap - EXIT HUP INT TERM } wait_for_mapped_devices() { mapping="${TERA_SERIALD_MAPPING:-/etc/tera/serial-map.json}" [ -r "$mapping" ] || { echo "missing serial mapping $mapping" >&2; return 1; } devices="$(sed -n 's/.*"device":[[:space:]]*"\([^"]*\)".*/\1/p' "$mapping")" || return 1 [ "$(printf '%s\n' "$devices" | grep -c '^/dev/ttyWCH[0-9][0-9]*$')" -eq 48 ] || { echo "serial mapping must contain exactly 48 WCH devices" >&2 return 1 } attempt=0 while [ "$attempt" -lt 100 ]; do missing=0 for device in $devices; do [ -c "$device" ] || { missing=1; break; } done [ "$missing" -eq 0 ] && return 0 attempt=$((attempt + 1)) sleep 0.1 done echo "mapped WCH devices did not appear within 10 seconds" >&2 return 1 } prepare_runtime() { ensure_account || return 1 wait_for_mapped_devices || return 1 mkdir -p /run/tera-seriald /var/log/tera || return 1 chown tera:tera /run/tera-seriald /var/log/tera || return 1 for device in /dev/ttyWCH*; do [ -c "$device" ] || continue chgrp tera "$device" || return 1 chmod 0660 "$device" || return 1 done } load_environment() { if [ -r "$ENVFILE" ]; then set -a . "$ENVFILE" set +a fi } is_running() { [ -r "$PIDFILE" ] || return 1 pid="$(cat "$PIDFILE" 2>/dev/null)" [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null } start_service() { if is_running; then echo "tera-seriald is already running" return 0 fi rm -f "$PIDFILE" [ -x "$DAEMON" ] || { echo "missing $DAEMON" >&2; return 1; } load_environment prepare_runtime || return 1 start-stop-daemon -S -q -b -m -p "$PIDFILE" -c "$RUN_AS" -x "$DAEMON" } stop_service() { if ! is_running; then rm -f "$PIDFILE" return 0 fi start-stop-daemon -K -q -p "$PIDFILE" -x "$DAEMON" rm -f "$PIDFILE" } case "$1" in start) start_service ;; stop) stop_service ;; restart) stop_service && start_service ;; status) is_running ;; *) echo "Usage: $0 {start|stop|restart|status}" >&2; exit 1 ;; esac