AndrianBdn/urr-reboot

Tiny last-resort TCP reboot/info daemon for Linux boxes with failing storage

★ 0Forks 0ShellGitHub ↗Compare

README

urr-reboot

urr — a tiny TCP daemon for remotely rebooting (and checking on) a Linux box, even when its storage has gone bad.

The name is short for ultima ratio — the last resort. On a machine with a dying HDD, a normal reboot or systemctl reboot can hang forever waiting to flush a disk that will never respond, leaving you with a host you can reach but can't recycle. urr exposes a couple of "don't sync, just go" reboot paths (syscall_nosync, sysrqb) that force an immediate restart, so you can power-cycle the box over the network instead of walking to it.

It's a single static binary (~2.5 MB, no runtime deps) run as a systemd service.

⚠️ Security: the shared secret is sent in plaintext over TCP, and INFO needs no auth at all. Only expose this on a trusted/management network (or over a VPN / SSH tunnel) and firewall the port. See Security model.

Install

No build toolchain required — the installer downloads a prebuilt binary from the latest GitHub release. Needs Linux + systemd + root and curl (or wget).

curl -fsSL https://raw.githubusercontent.com/andrianbdn/urr-reboot/main/install.sh \
  | sudo REBOOT_SECRET="choose-a-strong-secret" bash

Don't want to pick a secret? Omit it and one is generated and printed once:

curl -fsSL https://raw.githubusercontent.com/andrianbdn/urr-reboot/main/install.sh | sudo bash

The installer:

  1. detects your CPU arch (amd64 / arm64 / 386) and downloads that binary,
  2. verifies it against the release SHA256SUMS,
  3. installs it to /usr/local/sbin/urr,
  4. writes the secret to /etc/urr.env (mode 600, root-only),
  5. installs + enables + starts the urr.service systemd unit,
  6. verifies the service came up.

Pin a specific release with URR_VERSION:

curl -fsSL .../install.sh | sudo URR_VERSION=v1.0.0 REBOOT_SECRET=… bash

Protocol

One newline-terminated command per TCP connection on port 987:

Command Auth Reply
INFO none uptime=… loadavg=… … …
REBOOT:<method>:<secret> secret ok or error: …

Reboot methods

Method What it does Syncs disk? Use when…
systemd systemctl reboot --force --no-wall yes the box is basically healthy
syscall sync(2) then reboot(2) RESTART yes systemd is wedged but the disk still works
syscall_nosync reboot(2) RESTART, no sync no the disk is hung and you accept data loss
sysrqb echo b > /proc/sysrq-trigger (immediate hard reset) no last resort — instant, like the reset pin

syscall_nosync and sysrqb skip flushing to disk on purpose. That's the whole point on a box with failing storage, but it means unsaved data is lost. Reach for systemd/syscall first; drop to the nosync paths only when a clean reboot hangs.

sysrqb writes 1 to /proc/sys/kernel/sysrq first to make sure SysRq is enabled, then triggers the reboot — so it works even if SysRq was switched off. (Writes to /proc/sysrq-trigger already bypass that mask on most kernels; enabling it first just makes it deterministic. Both are procfs writes, unaffected by a wedged disk.)

Usage

From another machine (replace HOST):

# Check it's alive (no secret required)
printf 'INFO\n' | nc HOST 987
# -> uptime=3h12m4s loadavg=0.08 0.03 0.01

# Clean reboot
printf 'REBOOT:systemd:%s\n' "$REBOOT_SECRET" | nc HOST 987
# -> ok

# Disk is hung — force it without syncing
printf 'REBOOT:syscall_nosync:%s\n' "$REBOOT_SECRET" | nc HOST 987

# Nuclear option (immediate, no sync)
printf 'REBOOT:sysrqb:%s\n' "$REBOOT_SECRET" | nc HOST 987

Any TCP client works — nc/ncat, socat - TCP:HOST:987, even a raw /dev/tcp redirect in bash. Each connection has a 10-second deadline.

Managing the service

systemctl status urr
journalctl -u urr -f        # watch reboot requests as they arrive
systemctl restart urr       # e.g. after changing the secret in /etc/urr.env

To change the secret: edit /etc/urr.env, then systemctl restart urr.

Uninstall

curl -fsSL https://raw.githubusercontent.com/andrianbdn/urr-reboot/main/uninstall.sh | sudo bash

…or, from a checkout: sudo ./uninstall.sh. Removes the unit, binary, and /etc/urr.env.

Security model

This daemon can power-cycle the machine, so treat the port as sensitive:

  • Plaintext secret. REBOOT:…:<secret> travels unencrypted. Anyone who can sniff the connection sees it. Use it only over trusted links (a management VLAN, WireGuard/VPN, or an SSH tunnel).

  • Unauthenticated INFO. Uptime/load is exposed to anyone who can connect.

  • No rate limiting / lockout. Don't expose port 987 to the public internet. Restrict it at the firewall, e.g.:

    # allow only the management subnet to reach urr
    iptables -A INPUT -p tcp --dport 987 -s 10.0.0.0/24 -j ACCEPT
    iptables -A INPUT -p tcp --dport 987 -j DROP
  • The secret lives in /etc/urr.env (mode 600), not baked into the systemd unit, so it stays out of world-readable /etc/systemd/system.

Building from source

You don't need this to install — releases ship prebuilt binaries — but:

make build        # native build -> ./urr
make build-all    # cross-compile amd64/arm64/386 -> dist/
make dist         # build-all + unit + SHA256SUMS (what a release contains)
make vet          # go vet

Requires a Go toolchain (the daemon is Linux-only; //go:build linux).

Releases & CI

  • .github/workflows/ci.yml — on every push/PR: go vet + cross-compile all three targets.
  • .github/workflows/release.yml — on a v* tag: builds urr-linux-{amd64, arm64,386}, copies urr.service, generates SHA256SUMS, and publishes a GitHub release with those assets (the exact files the installer pulls).

Cut a release:

git tag v1.0.0
git push origin v1.0.0

Repo layout

urr-reboot/
├── main.go                      # the daemon (Linux-only; //go:build linux)
├── go.mod / go.sum              # module + pinned golang.org/x/sys
├── urr.service                  # systemd unit (shipped as a release asset)
├── install.sh                   # pipeable installer (downloads from releases)
├── uninstall.sh                 # tear down
├── Makefile                     # build / build-all / dist / vet
├── .github/workflows/ci.yml     # vet + cross-compile
├── .github/workflows/release.yml# build + publish release on tag
└── README.md

Contributors

AndrianBdn

Issues