Terminal UI for a headless Jellyfin media server on Arch / Omarchy
Find a file
28allday 77929de645 Spin down idle media drives by default via a watcher service
Drives on a headless Jellyfin box ran hot 24/7: NAS-class drives (WD
Red, IronWolf) ship with no idle timer at all. v0.1.2 stopped tuistream
from keeping them awake; this makes them actually go to sleep.

Spin-down is a DEFAULT, not a feature: when the inventory shows
spinning media drives, the TUI silently installs/syncs the mechanism
and flashes what it did. [s] on Setup is the opt-OUT, remembered via
/etc/tuistream/spindown-off so the default never fights the user.

The mechanism is a tiny systemd service (tuistream --spindown-watch)
that samples /proc/diskstats and issues `hdparm -y` to any target drive
idle past 3 minutes. We deliberately do NOT use the drive's own standby
timer (hdparm -S): the 10TB helium WD Reds advertise it and then ignore
it — verified on real hardware (fresh -S 36, zero I/O, four minutes,
still active/idle). Forcing standby from outside works on everything.
An rc shipped the udev+hdparm -S approach; the plans clean its rule up.

Safety: targets are spinning (sysfs rotational), top-level, real disks,
never a system disk — same classifier the rest of Setup trusts. The
watcher's own probes can't disturb drives: /proc/diskstats and sysfs are
kernel memory, and hdparm -C (CHECK POWER MODE) doesn't wake or reset
anything. The unit's ExecStart points at the running binary and the
auto-sync rewrites it when the content goes stale (e.g. binary moved
from a dev path to /usr/local/bin).

hdparm joins the dependency pre-flight. Verified end-to-end on moviebox
(2x WD Red 10TB SATA + IronWolf 10TB USB): auto-applied on launch, all
three drives reached standby after 3 idle minutes, Monitor shows them
as "asleep" without waking them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:28:39 +01:00
cmd/tuistream Spin down idle media drives by default via a watcher service 2026-06-04 16:28:39 +01:00
internal Spin down idle media drives by default via a watcher service 2026-06-04 16:28:39 +01:00
.gitignore Initial commit: TUISTREAM — headless Jellyfin TUI for Omarchy/Arch 2026-05-31 16:47:18 +01:00
go.mod Initial commit: TUISTREAM — headless Jellyfin TUI for Omarchy/Arch 2026-05-31 16:47:18 +01:00
go.sum Initial commit: TUISTREAM — headless Jellyfin TUI for Omarchy/Arch 2026-05-31 16:47:18 +01:00
install.sh Initial commit: TUISTREAM — headless Jellyfin TUI for Omarchy/Arch 2026-05-31 16:47:18 +01:00
LICENSE Initial commit: TUISTREAM — headless Jellyfin TUI for Omarchy/Arch 2026-05-31 16:47:18 +01:00
README.md Spin down idle media drives by default via a watcher service 2026-06-04 16:28:39 +01:00

TUISTREAM (tuistream)

A terminal UI for setting up and running a headless Jellyfin media server on Arch / Omarchy — over SSH, with no desktop environment. It installs Jellyfin, attaches and mounts media drives (single disks or btrfs RAID pools), opens the firewall, and shows a live health monitor, all without dropping you to a shell or switching terminals mid-task.

TUISTREAM is an independent setup/management tool for Jellyfin. It is not affiliated with the Jellyfin project; "Jellyfin" is used here descriptively.

Features

  • Setup — install / uninstall Jellyfin, open or close the firewall ports, copy the server's web address to your clipboard (works over SSH + tmux), and move Jellyfin's library storage onto a media drive.
  • Drive spin-down by default — spinning media drives are automatically put to sleep after 3 idle minutes (cooler, quieter) by a tiny background watcher that survives reboots — and works even on NAS drives that ignore their own firmware idle timer (looking at you, WD Red). System drives are never touched; s opts out if you want 24/7 spinning.
  • Add media drive — attach a spare disk or partition: keep its existing filesystem or format it (btrfs / ext4 / xfs), or combine 2+ disks into a btrfs RAID pool (1 / 0 / 5 / 10). The boot drive is never offered — the classifier follows every candidate down through LUKS / LVM / RAID to its physical disk and refuses anything hosting /, /boot, or swap.
  • Import an existing btrfs pool — re-attach a multi-device pool from a previous setup non-destructively (mounts it as-is; never formats).
  • Safe with existing media — keeping a filesystem previews what's on it, skips creating starter folders when it already has content, and grants Jellyfin read access recursively so existing media is actually visible to the scanner.
  • Manage — copy media in from an external drive (with a file picker), mount / eject drives, and rename or delete files, each behind a confirmation.
  • Monitor — per-core CPU, load / memory / uptime, per-mount capacity, btrfs pool error counters, and per-disk SMART health — for a box you only reach over SSH.
  • Single static binary; missing tools (btrfs-progs, xfsprogs, acl, …) are installed on first launch.

Install

One line, nothing to clone:

curl -fsSL https://raw.githubusercontent.com/28allday/TUISTREAM/main/install.sh | bash

This installs the right binary for your architecture into /usr/local/bin (using sudo if needed) so both tuistream and sudo tuistream resolve. Pin a version with TUISTREAM_VERSION=v0.1.0, or change the prefix with PREFIX=/opt.

TUISTREAM installs to a system path on purpose: it's a root-by-design tool (installs Jellyfin, edits /etc/fstab, mounts drives), so it must be on the sudo secure_path~/.local/bin is not.

Usage

sudo tuistream                 # full TUI
tuistream --read-only          # inventory views only, no root, no actions

Run with sudo, it acts on behalf of the real user behind the sudo invocation ($SUDO_USER): media drives mount under /media/<user>/ and its Omarchy theme is used. tab / shift+tab switch tabs, r refreshes, q quits; each tab shows its key bar at the bottom.

Getting started

A first-run, from an empty box to a working server — all from the Setup tab (sudo tuistream):

  1. Install Jellyfin — press i and confirm. TUISTREAM pulls Jellyfin from the official Arch repo and starts the service.
  2. Attach storage for your media:
    • A spare disk → press a (add drive), pick it, choose Keep existing filesystem or format it, and name it. It mounts at /media/<user>/<name>.
    • A multi-disk btrfs pool from a previous setup → press p (import pool) to re-attach it untouched. (p only shows when such a pool is present.)
    • On a fresh drive, TUISTREAM seeds library folders for you under <mount>/JellyfinMedia/: Movies, Shows, Music, Books, Home Videos, Music Videos. Copy your media into these (the Manage tab's c can copy in from an external drive).
  3. Open the firewall — press f so other devices on your LAN can reach the server (ports 8096/tcp web UI and 7359/udp discovery).
  4. Open the web UI — press y to copy the server's address (http://<host>:8096) to your clipboard, even over SSH, then open it in a browser on any LAN device.
  5. Create your libraries in Jellyfin — in the web UI's first-run wizard, add a library for each type and point it at the matching folder under /media/<user>/<name>/JellyfinMedia/ (e.g. Movies…/JellyfinMedia/Movies). The folder names already match Jellyfin's content types.

Optional: press j to move Jellyfin's own library database and metadata off the OS drive onto a media drive (handy on a small boot SSD).

Note on drive spin-down: when TUISTREAM sees spinning media drives it automatically installs a small watcher service that spins them down after 3 idle minutes, so they don't run hot 24/7. (It watches actual disk I/O rather than trusting the drive's own idle timer, which many NAS drives silently ignore.) The first play after a sleep takes a few seconds while the drives wake. Press s to opt out — TUISTREAM remembers and won't re-apply the default.

Build from source

Needs Go 1.26+.

git clone https://github.com/28allday/TUISTREAM
cd TUISTREAM
./install.sh    # builds, then installs to /usr/local/bin

License

MIT — see LICENSE. TUISTREAM runs Jellyfin (GPL-2.0) as a separate process; it does not bundle or link it. The terminal UI is built on the Charm libraries (MIT).