Fixes and improvements
This commit is contained in:
1 parent
ce0fe626b2
commit
82f50ae5c0
48 files changed
+63
-63
No files matched your search
+2
-17
@@ -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
File mode changed.
+2
-1
@@ -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
File mode changed.
Executable → Regular
+6
-20
@@ -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
@@ -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
File mode changed.
Executable → Regular
+10
-12
@@ -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
File mode changed.
Reference in new issue
Block a user