docs: tell the whole daemon story, and stop denying it exists

An audit of the README and the site against the implementation found 38
places the docs were missing, stale, or wrong about daemon mode. Two were
outright false: the community page still answered "want a queue that
outlives the command?" with "slskd has that", and the home page promised
"no daemon, no API to stand up" fifty lines before the section that
describes both.

The download-box walkthrough now tells the truth about its sharp edges: a
pushed folder or share list is live-only on a remote daemon, whose own
config.toml wins at restart; `shares add` validates the path on the
machine you type it on; and a `get` that reaches its `--timeout` cancels
the daemon-side transfer. Its shell example also runs as written now — the
old block put a second command after a foreground daemon that never
returns.

The reference material catches up with the feature: `daemon` and its
subcommands join the command list, the config table gains the
daemon/daemon_token/--no-daemon rows, nine settings become eleven,
`daemon status` and `daemon token` get documented record shapes, exit 3
covers daemons, `queued` joins the serve statuses, the credential-free
count goes 13 to 19, and the action-command count 4 to 6.

And the surprises are written down rather than discovered: the daemon does
not sweep the wishlist on its own; Windows has no socket and so no
auto-attach; the attached TUI's settings form edits the daemon's folders
and rewrites this machine's config.toml; `serve` monitors a daemon's
uploads instead of starting a second server; `whoami` reports the
daemon's world, with --no-daemon as the way out. The OpenRPC section names
the six event methods and the session-wide download.set_dir rule.

Site typechecks, lints, and builds through prerender; every claim was
verified against the cited code before it was written.
This commit is contained in:
Michel de Graaf
2026-07-29 09:22:02 +02:00
parent f1b667f7f9
commit ab5cef8cfe
5 changed files with 221 additions and 69 deletions
+31 -5
View File
@@ -125,7 +125,7 @@ const PLATFORMS: readonly PlatformNote[] = [
config: '%APPDATA%\\soulseek-rs\\config\\config.toml',
state: '%APPDATA%\\soulseek-rs\\data\\state',
downloads: '%USERPROFILE%\\Downloads\\Soulseek',
note: 'Uses the standard known-folder locations for roaming app data.',
note: 'Uses the standard known-folder locations for roaming app data. Without Unix sockets, a daemon here needs --bind 127.0.0.1:5030, and clients reach it with config set daemon plus config set daemon_token; that token file carries no permissions of its own.',
},
]
@@ -207,6 +207,26 @@ const SETTINGS: readonly Setting[] = [
</>
),
},
{
key: 'daemon',
fallback: 'the local socket, if one is running',
body: (
<>
A daemon to drive instead of logging in here, as <code>host:port</code> or a Unix
socket path.
</>
),
},
{
key: 'daemon_token',
fallback: 'unset',
body: (
<>
The secret a remote daemon asks for, printed by <code>daemon token</code> on the
machine running it. A local socket needs none.
</>
),
},
]
const CONFIG_EXAMPLE: TermLine[] = [
@@ -431,14 +451,17 @@ const Platforms = () => (
<code>config.toml</code> is layered: flags beat environment variables, which
beat the file, which beats the defaults. State (searches, downloads, rooms) is
versioned JSON and restores on restart; soulseek-rs sets anything unreadable
aside as <code>.bak</code> rather than overwriting it.
aside as <code>.bak</code> rather than overwriting it. Attached to a daemon, that
state is the daemon&rsquo;s, and the state folder also holds its{' '}
<code>daemon.sock</code> and <code>daemon.token</code>, both 0600 on macOS and
Linux.
</FeatureCard>
</Cols>
</Section>
)
const Config = () => (
<Section id="config">
<SectionHead eyebrow="config.toml" title="Nine settings, all optional.">
<SectionHead eyebrow="config.toml" title="Eleven settings, all optional.">
Every one has a working default, so an empty file, or no file at all, is valid.{' '}
<Code>config list</Code> prints the effective value of each.
</SectionHead>
@@ -471,7 +494,9 @@ const Config = () => (
reads one setting and <Code>config set</Code> writes one. It rejects
unknown keys, so a typo fails instead of doing nothing.{' '}
<Code>--no-config</Code> ignores the file entirely and{' '}
<Code>--config FILE</Code> points at another one.
<Code>--config FILE</Code> points at another one. <Code>wishlist</Code> is in
the file too, written by <Code>wish add</Code> rather than{' '}
<Code>config set</Code>, since a standing search may itself contain a comma.
</p>
</Callout>
</div>
@@ -497,7 +522,8 @@ const Uninstall = () => (
{ t: 'cmd', text: 'cargo uninstall soulseek-rs' },
{ t: 'cm', text: '# brew only: drop the tap it came from' },
{ t: 'cmd', text: 'brew untap michel/tap' },
{ t: 'cm', text: '# config and state: searches, downloads, rooms, messages' },
{ t: 'cm', text: '# config and state: searches, downloads, rooms,' },
{ t: 'cm', text: '# messages, the daemon token' },
{
t: 'cmd',
text: 'rm -rf ~/.config/soulseek-rs ~/.local/share/soulseek-rs',