feat(config): remember which daemon to use, and document the download box
A remote daemon could only be named with --daemon and --daemon-token on every invocation, which made the one workflow daemon mode is most useful for — a headless download box driven from a laptop — a matter of retyping a 64-character token. Both are now config keys, so it is two 'config set' commands once and plain commands thereafter. Fixes a bug that setup exposed: the up-front daemon status probe read the token from the command line only, so with the token in the config file it failed silently and every command that names a path or a server fell back to this machine's defaults — whoami claiming downloads land in the laptop's home directory when they land on the box. The regression test was checked by reintroducing the bug. Documents the download-box workflow in plain terms across the README, the website and the agent skill, and says plainly that the schema is open: OpenRPC, served by the daemon itself, so an Android, iOS or web remote is something anyone can build without asking.
This commit is contained in:
@@ -70,6 +70,8 @@ impl Daemon {
|
||||
"pw",
|
||||
"--listener-port",
|
||||
&free_port()?.to_string(),
|
||||
"--download-dir",
|
||||
state.join("downloads").to_str()?,
|
||||
"daemon",
|
||||
"--bind",
|
||||
&format!("127.0.0.1:{port}"),
|
||||
@@ -492,3 +494,74 @@ fn scratch(label: &str) -> PathBuf {
|
||||
std::fs::create_dir_all(&path).expect("scratch directory");
|
||||
path
|
||||
}
|
||||
|
||||
/// A client configured for a remote daemon reports the *daemon's* world.
|
||||
///
|
||||
/// This is the download-box workflow: the token lives in the client's config
|
||||
/// file rather than on its command line, and it has no Soulseek account of its
|
||||
/// own. Reading the token from the flag alone made the status probe fail
|
||||
/// silently, and every command that names a path or a server then quietly fell
|
||||
/// back to this machine's defaults — `whoami` claiming downloads land in the
|
||||
/// laptop's home directory when they land on the box.
|
||||
#[test]
|
||||
fn a_config_driven_remote_client_reports_the_daemons_world() {
|
||||
let daemon = daemon_or_skip!();
|
||||
let config = scratch("remote-client-config");
|
||||
let state = scratch("remote-client-state");
|
||||
|
||||
// A laptop that knows only where the daemon is. No username, no password,
|
||||
// no server — exactly what the documented setup produces.
|
||||
std::fs::write(
|
||||
config.join("config.toml"),
|
||||
format!(
|
||||
"daemon = \"127.0.0.1:{}\"\ndaemon_token = \"{}\"\n",
|
||||
daemon.port,
|
||||
daemon.token()
|
||||
),
|
||||
)
|
||||
.expect("config file");
|
||||
|
||||
let mut command = Command::new(BIN);
|
||||
for name in CLI_ENV_VARS {
|
||||
command.env_remove(name);
|
||||
}
|
||||
command.env("SOULSEEK_CONFIG_DIR", &config);
|
||||
command.env("SOULSEEK_STATE_DIR", &state);
|
||||
command.current_dir(std::env::temp_dir());
|
||||
command.args(["--json", "whoami"]);
|
||||
let output = command.output().expect("the binary should run");
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"a client configured for a remote daemon should just work: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
let record: serde_json::Value = serde_json::from_str(
|
||||
String::from_utf8_lossy(&output.stdout)
|
||||
.lines()
|
||||
.find(|line| !line.trim().is_empty())
|
||||
.expect("one record"),
|
||||
)
|
||||
.expect("valid JSON");
|
||||
|
||||
assert_eq!(
|
||||
record["user"], "cli_e2e_proto",
|
||||
"the daemon's account, not this machine's"
|
||||
);
|
||||
assert!(
|
||||
record["server"]
|
||||
.as_str()
|
||||
.is_some_and(|server| server.starts_with("127.0.0.1:")),
|
||||
"the server the daemon is connected to, not a local default: {record}"
|
||||
);
|
||||
assert!(
|
||||
record["download_dir"].as_str().is_some_and(
|
||||
|dir| dir.starts_with(std::env::temp_dir().to_str().unwrap())
|
||||
),
|
||||
"the directory the bytes actually land in, on the daemon's host, not \
|
||||
this machine's Downloads folder: {record}"
|
||||
);
|
||||
|
||||
let _ = std::fs::remove_dir_all(&config);
|
||||
let _ = std::fs::remove_dir_all(&state);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user