Fixes and improvements

This commit is contained in:
imengying committed 2026-09-12 10:44:35 +08:00
1 parent ce0fe626b2
commit 82f50ae5c0
48 files changed
+63 -63

No files matched your search

+2 -17
View File
@@ -8,9 +8,6 @@ STATE_ROOT="/etc/nodeflare"
STATE_DIR="$STATE_ROOT/agent"
SERVICE_FILE="/etc/systemd/system/$SERVICE_NAME.service"
OPENRC_FILE="/etc/init.d/$SERVICE_NAME"
# Holds NODEFLARE_AGENT_TOKEN for systemd; a 0600 file inside the 0750 state dir
# is readable only by root/uninstall, unlike Environment= in the unit.
TOKEN_ENV_FILE="$STATE_DIR/agent.env"
temporary=""
backup_agent=""
backup_service=""
@@ -350,10 +347,6 @@ install_agent() {
mv "$temporary" "$AGENT_FILE"
log "正在启动 $init_system 服务"
if [ "$init_system" = "systemd" ]; then
# The token goes into a 0600 file rather than Environment=, which any local
# user can read back with `systemctl show`.
printf 'NODEFLARE_AGENT_TOKEN=%s\n' "$token" > "$TOKEN_ENV_FILE"
chmod 600 "$TOKEN_ENV_FILE"
printf '%s\n' \
'[Unit]' \
'Description=NodeFlare Agent' \
@@ -366,7 +359,7 @@ install_agent() {
'Restart=always' \
'RestartSec=10' \
'NoNewPrivileges=true' \
"EnvironmentFile=$TOKEN_ENV_FILE" \
"Environment=NODEFLARE_AGENT_TOKEN=$token" \
"Environment=NODEFLARE_STATE_DIR=$STATE_DIR" \
'' \
'[Install]' \
@@ -411,15 +404,7 @@ load_installed_agent_config() {
service_exec=$(sed -n 's/^ExecStart=//p' "$SERVICE_FILE" | head -n 1)
endpoint=$(printf '%s\n' "$service_exec" | sed -n 's/.* -e \([^[:space:]]*\) -i \([0-9][0-9]*\)$/\1/p')
interval=$(printf '%s\n' "$service_exec" | sed -n 's/.* -e \([^[:space:]]*\) -i \([0-9][0-9]*\)$/\2/p')
token=""
if [ -f "$TOKEN_ENV_FILE" ]; then
token=$(sed -n 's/^NODEFLARE_AGENT_TOKEN=//p' "$TOKEN_ENV_FILE" | head -n 1)
fi
# Fall back to the unit file so installs created before the token moved
# into an EnvironmentFile still update cleanly.
if [ -z "$token" ]; then
token=$(sed -n 's/^Environment=NODEFLARE_AGENT_TOKEN=//p' "$SERVICE_FILE" | head -n 1)
fi
token=$(sed -n 's/^Environment=NODEFLARE_AGENT_TOKEN=//p' "$SERVICE_FILE" | head -n 1)
;;
openrc)
[ -f "$OPENRC_FILE" ] || fail "未找到 Agent 服务配置,请先完成安装"
Executable → Regular
View File
File mode changed.
+2 -1
View File
@@ -574,6 +574,7 @@ pub(crate) fn live_sender_loop(endpoint: &str, token: &str, worker: LiveSenderWo
stats.live_persistence_failed();
drop_socket = true;
} else {
backoff.reset();
prune_live_queue(&pending, ack.persisted_through_ts);
accepted_through = accepted_through.max(ack.persisted_through_ts);
next_probe_at = Instant::now() + ack_persist_interval(&ack);
@@ -644,7 +645,6 @@ pub(crate) fn live_sender_loop(endpoint: &str, token: &str, worker: LiveSenderWo
continue;
}
stats.live_batch_sent(batch_len);
backoff.reset();
if let Some(timestamp) = batch_last {
accepted_through = accepted_through.max(timestamp);
}
@@ -663,6 +663,7 @@ pub(crate) fn live_sender_loop(endpoint: &str, token: &str, worker: LiveSenderWo
stats.live_persistence_failed();
drop_socket = true;
} else {
backoff.reset();
prune_live_queue(&pending, ack.persisted_through_ts);
accepted_through = accepted_through.max(ack.persisted_through_ts);
next_probe_at = Instant::now() + ack_persist_interval(&ack);
Executable → Regular
View File
File mode changed.
Executable → Regular
+6 -20
View File
@@ -120,12 +120,8 @@ struct CliOptions {
#[arg(short = 'e', value_name = "URL")]
endpoint: String,
/// Agent token
#[arg(short = 't', value_name = "TOKEN", conflicts_with = "token_file")]
#[arg(short = 't', value_name = "TOKEN")]
token: Option<String>,
/// Read the Agent token from a file instead of -t, which keeps it out of
/// the process list
#[arg(long = "token-file", value_name = "PATH")]
token_file: Option<PathBuf>,
/// Initial report interval in seconds (15-3600)
#[arg(short = 'i', value_name = "SECONDS", default_value_t = 60)]
interval: u64,
@@ -455,26 +451,16 @@ fn advance_deadline(mut deadline: Instant, interval: Duration, now: Instant) ->
deadline
}
/// Reads an Agent token from a file, trimming surrounding whitespace (editors
/// and shell redirection usually append a trailing newline).
fn read_token_file(path: &Path) -> Result<String> {
let contents = fs::read_to_string(path)
.map_err(|error| format!("failed to read token file {}: {error}", path.display()))?;
Ok(contents.trim().to_string())
}
fn runtime_config(options: &CliOptions) -> Result<RuntimeConfig> {
let interval = options.interval;
if !(15..=3600).contains(&interval) {
return Err("interval must be between 15 and 3600 seconds".into());
}
let token = match &options.token {
Some(token) => token.clone(),
None => match options.token_file.as_deref() {
Some(path) => read_token_file(path)?,
None => env::var("NODEFLARE_AGENT_TOKEN").unwrap_or_default(),
},
};
let token = options
.token
.clone()
.or_else(|| env::var("NODEFLARE_AGENT_TOKEN").ok())
.unwrap_or_default();
let endpoint = options.endpoint.clone();
if token.is_empty() || token.len() > 512 || token.chars().any(char::is_whitespace) {
return Err("token is invalid".into());
Executable → Regular
+27 -2
View File
@@ -57,6 +57,8 @@ pub(crate) fn output_with_timeout(
mut process: Command,
timeout: Duration,
) -> Option<std::process::Output> {
#[cfg(unix)]
process.process_group(0);
let mut child = process
.stdin(Stdio::null())
.stdout(Stdio::piped())
@@ -75,13 +77,13 @@ pub(crate) fn output_with_timeout(
match child.try_wait() {
Ok(Some(status)) => break Some(status),
Ok(None) if started.elapsed() >= timeout => {
let _ = child.kill();
terminate_command_tree(&mut child);
let _ = child.wait();
break None;
}
Ok(None) => thread::sleep(Duration::from_millis(20)),
Err(_) => {
let _ = child.kill();
terminate_command_tree(&mut child);
let _ = child.wait();
break None;
}
@@ -97,6 +99,29 @@ pub(crate) fn output_with_timeout(
})
}
fn terminate_command_tree(child: &mut Child) {
#[cfg(unix)]
{
let group = format!("-{}", child.id());
let _ = Command::new("kill")
.args(["-KILL", "--", group.as_str()])
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status();
}
#[cfg(target_os = "windows")]
{
let _ = Command::new("taskkill")
.args(["/PID", &child.id().to_string(), "/T", "/F"])
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status();
}
let _ = child.kill();
}
pub(crate) fn command(name: &str, args: &[&str]) -> String {
let mut process = Command::new(name);
process.args(args);
Executable → Regular
View File
File mode changed.
Executable → Regular
+10 -12
View File
@@ -430,6 +430,16 @@ fn remote_commands_are_terminated_after_the_deadline() {
assert!(result.result.contains("已终止"));
}
#[cfg(unix)]
#[test]
fn command_timeout_terminates_descendants_without_waiting_for_pipes() {
let mut command = std::process::Command::new("sh");
command.args(["-c", "sleep 2 & wait"]);
let started = Instant::now();
assert!(super::output_with_timeout(command, Duration::from_millis(50)).is_none());
assert!(started.elapsed() < Duration::from_secs(1));
}
#[test]
fn combined_remote_output_is_bounded_at_utf8_boundaries() {
let result = remote_result_text(
@@ -628,18 +638,6 @@ fn parses_cli_options() {
assert!(parsed.once);
assert!(CliOptions::try_parse_from(["agent", "-t"]).is_err());
assert!(CliOptions::try_parse_from(["agent", "-t", "first", "-t", "second"]).is_err());
assert!(
CliOptions::try_parse_from([
"agent",
"-e",
"https://monitor.example.com",
"-t",
"agent-token",
"--token-file",
"/run/nodeflare/token",
])
.is_err()
);
assert!(CliOptions::try_parse_from(["agent", "--once", "--collect"]).is_err());
}
Executable → Regular
View File
File mode changed.