From 2aaa5ac09165f3cca3b2d71b7442e1658e62c6ef Mon Sep 17 00:00:00 2001 From: reverse Date: Wed, 12 Aug 2026 16:01:58 +0000 Subject: [PATCH] deploy: persistent systemd unit running a pinned release binary with Restart=on-failure + boot enable, plus a liveness timer that restarts the daemon if a register round-trip stops answering --- deploy/echoircd-dev.service | 19 ++++++++++++++++ deploy/echoircd-liveness.service | 8 +++++++ deploy/echoircd-liveness.timer | 10 ++++++++ scripts/liveness.sh | 39 ++++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+) create mode 100644 deploy/echoircd-dev.service create mode 100644 deploy/echoircd-liveness.service create mode 100644 deploy/echoircd-liveness.timer create mode 100755 scripts/liveness.sh diff --git a/deploy/echoircd-dev.service b/deploy/echoircd-dev.service new file mode 100644 index 0000000..5bb3a8b --- /dev/null +++ b/deploy/echoircd-dev.service @@ -0,0 +1,19 @@ +[Unit] +Description=echoIRCd (production) — native Rust ircd +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=debian +WorkingDirectory=/home/debian/irc/ircd/echoIRCd +LimitNOFILE=200000 +# runs a PINNED release binary (bin/echoircd), not target/debug — so a stray +# `cargo build` never changes what a restart would launch. Deploy = copy a fresh +# `cargo build --release` binary over bin/echoircd, then `systemctl restart`. +ExecStart=/home/debian/irc/ircd/echoIRCd/bin/echoircd /home/debian/irc/ircd/echoIRCd/echoircd.conf +Restart=on-failure +RestartSec=2 + +[Install] +WantedBy=multi-user.target diff --git a/deploy/echoircd-liveness.service b/deploy/echoircd-liveness.service new file mode 100644 index 0000000..c7a44f1 --- /dev/null +++ b/deploy/echoircd-liveness.service @@ -0,0 +1,8 @@ +[Unit] +Description=echoIRCd liveness probe — restart the daemon if it stops responding + +[Service] +Type=oneshot +# a hung (but not exited) daemon isn't caught by Restart=on-failure; this probe +# does a real register round-trip and restarts the service if it doesn't answer. +ExecStart=/home/debian/irc/ircd/echoIRCd/scripts/liveness.sh 6767 diff --git a/deploy/echoircd-liveness.timer b/deploy/echoircd-liveness.timer new file mode 100644 index 0000000..c0eff83 --- /dev/null +++ b/deploy/echoircd-liveness.timer @@ -0,0 +1,10 @@ +[Unit] +Description=Run the echoIRCd liveness probe periodically + +[Timer] +OnBootSec=2min +OnUnitActiveSec=2min +AccuracySec=15s + +[Install] +WantedBy=timers.target diff --git a/scripts/liveness.sh b/scripts/liveness.sh new file mode 100755 index 0000000..451fdfb --- /dev/null +++ b/scripts/liveness.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# echoIRCd liveness probe: do a real NICK/USER register round-trip on the plaintext +# port and, if the daemon doesn't answer with a 001 welcome, restart it. Catches a +# hung core (process alive but not serving), which Restart=on-failure alone can't. +# Run by echoircd-liveness.timer. +set -u +PORT="${1:-6767}" + +if python3 - "$PORT" <<'PY' +import socket, sys, time +port = int(sys.argv[1]) +try: + s = socket.create_connection(("127.0.0.1", port), timeout=5) + s.settimeout(5) + s.sendall(b"NICK livecheck\r\nUSER lc 0 * :liveness\r\n") + data = b"" + end = time.time() + 6 + while time.time() < end: + d = s.recv(4096) + if not d: + break + data += d + if b" 001 " in data: + break + try: + s.sendall(b"QUIT :liveness\r\n") + except OSError: + pass + s.close() + sys.exit(0 if b" 001 " in data else 1) +except OSError: + sys.exit(1) +PY +then + exit 0 +fi + +echo "echoircd liveness: no 001 from 127.0.0.1:${PORT} within timeout — restarting echoircd-dev.service" +systemctl restart echoircd-dev.service