The daemon¶
There is one long-running process, the daemon, and everything else is a
thin client that connects to it over a Unix domain socket
($XDG_DATA_HOME/torrnado/daemon.sock by default, usually
~/.local/share/torrnado/daemon.sock).
torrnado(no arguments) attaches the TUI to the daemon. If nothing is listening on the socket, it spawnstorrnado daemonas a detached background process first (logging to$XDG_DATA_HOME/torrnado/daemon.log), waits for it to come up, then attaches. Quitting the TUI (q) does not stop the daemon -- your torrents keep running.torrnado daemonruns the engine in the foreground until interrupted (Ctrl-C) or sent SIGTERM. This is what gets spawned in the background automatically, but you can also run it yourself directly if you'd rather manage it with systemd/launchd/tmux than let torrnado manage it.torrnado add/remove/pause/resume/recheck/priority/limit/move/listare scriptable passthroughs to a running daemon (spawning one if needed, same as the bare command). Everything you can do in the TUI's command palette, you can also do from a shell script.
To actually stop the daemon: send it SIGTERM/SIGINT (pkill -f "torrnado daemon",
or kill the pid from its log line) -- there's no torrnado stop
subcommand, since a daemon that's still seeding is a normal, intentional
state to leave it in.
Running it unattended¶
The daemon is the whole program; the TUI and the CLI are just clients. So
a headless box that downloads things is torrnado daemon running under
whatever supervises processes there (systemd, launchd, tmux -- it makes
no difference to torrnado), driven over SSH.
It comes back after a restart. Every change is written to
<state_dir>/session.json -- the torrent list, paused state, save paths,
per-torrent rate limits, per-file priorities, when each was added -- next
to a copy of each torrent's metainfo in <state_dir>/torrents/. On start
the daemon re-adds them all, and logs how many came back. Data already on
disk is not re-downloaded or re-verified: the piece-completion database
was always persistent, and this is what tells the daemon which torrents
to look it up for.
A session file that cannot be read is logged and skipped, one bad record
at a time. A server that refuses to boot over a malformed record is worse
than one that comes back with fewer torrents, so it starts either way --
journalctl is where you find out which happened.
It can require a VPN. vpn.required = true holds every transfer while
the machine is not on one, and lets them go again by itself when it comes
back -- which is the case a box left running for weeks actually hits, when
the VPN client reconnects at 4am and nobody is watching. It is off by
default. Read what it does and does not cover in
Configuration before relying
on it.
Logs go to stderr as text with timestamps and levels, which is what a service manager wants: journald timestamps and stores it with no further configuration.
time=2026-08-05T13:26:28.488-04:00 level=INFO msg="daemon starting" pid=83154 ...
time=2026-08-05T13:26:28.491-04:00 level=INFO msg="session restored" torrents=2
time=2026-08-05T13:26:28.491-04:00 level=INFO msg="daemon ready" socket=... stream=127.0.0.1:64853
Set log.file to write to a file instead. SIGHUP reopens it, which is
what logrotate needs after renaming the old one away -- without that the
daemon goes on writing to a file that no longer has a name, and the disk
never gets the space back.
The torrent library's own output is captured into the same stream at
log.library_level, tagged src=torrent. It is much noisier than
torrnado is -- it reports every tracker that misbehaves -- which is why
it has its own level rather than sharing log.level. Panics still go
straight to stderr through the Go runtime, whatever log.file says, so
leave stderr attached to something.
Two things to know before deploying:
CGO_ENABLED=0changes the piece-completion database. With cgo it is SQLite (.torrent.dbin the download directory); without it, bbolt (.torrent.bolt.db). Neither reads the other's file, so a data directory populated by one build re-verifies from scratch under the other. Pin the choice deliberately when you build for a server, and keep it pinned.- Previews are loopback-only. The stream server binds
127.0.0.1on an OS-assigned port with a per-session token, sotorrnado previewURLs from a remote daemon are only reachable through an SSH tunnel (ssh -L 8080:127.0.0.1:<port> server). That is the intended design, not an oversight: there is no authentication anywhere in torrnado beyond the filesystem permissions on the socket, so nothing it serves should be reachable from a network.
Running a second daemon alongside the first means giving it its own
daemon_socket and its own state_dir. Only the socket is guarded
against sharing (by a lock file); two daemons pointed at one state
directory will each restore the other's torrents and overwrite the
other's session file.
There is no remote control protocol. The socket is local by construction, and SSH already solves the problem properly.
Deployment recipes have their own pages: systemd and Docker.