#[cfg(target_os = "windows")]
use super::log_utils::find_latest_log_file;
#[cfg(target_os = "windows")]
use anyhow::Context;
#[cfg(target_os = "windows")]
use anyhow::Result;
#[cfg(target_os = "windows")]
pub(super) fn install_service(system: bool) -> Result<()> {
if system {
anyhow::bail!(
"The --system flag is only supported on Linux.\n\
On Windows, use the default scheduled task: freenet service install"
);
}
let exe_path = std::env::current_exe().context("Failed to get current executable path")?;
let exe_path_str = exe_path
.to_str()
.context("Executable path contains invalid UTF-8")?;
let run_command = format!("\"{}\" service run-wrapper", exe_path_str);
let hkcu = winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER);
let (run_key, _) = hkcu
.create_subkey(r"Software\Microsoft\Windows\CurrentVersion\Run")
.context("Failed to open registry Run key")?;
run_key
.set_value("Freenet", &run_command)
.context("Failed to write Freenet registry entry")?;
drop(
std::process::Command::new("schtasks")
.args(["/delete", "/tn", "Freenet", "/f"])
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status(),
);
println!("Freenet autostart registered successfully.");
println!();
println!("To start Freenet now:");
println!(" freenet service start");
println!();
println!("Freenet will start automatically when you log in.");
println!("A system tray icon will appear with status and controls.");
Ok(())
}
#[cfg(target_os = "windows")]
fn check_no_system_flag_windows(system: bool) -> Result<()> {
if system {
anyhow::bail!(
"The --system flag is only supported on Linux.\n\
On Windows, use the default service commands without --system."
);
}
Ok(())
}
#[cfg(target_os = "windows")]
pub fn stop_and_remove_service(_system: bool) -> Result<bool> {
let hkcu = winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER);
let run_key = hkcu
.open_subkey_with_flags(
r"Software\Microsoft\Windows\CurrentVersion\Run",
winreg::enums::KEY_READ | winreg::enums::KEY_WRITE,
)
.context("Failed to open registry Run key")?;
let had_registry = run_key.delete_value("Freenet").is_ok();
kill_freenet_service_processes();
let had_task = std::process::Command::new("schtasks")
.args(["/query", "/tn", "Freenet"])
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false);
if had_task {
drop(
std::process::Command::new("schtasks")
.args(["/delete", "/tn", "Freenet", "/f"])
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status(),
);
}
Ok(had_registry || had_task)
}
#[cfg(target_os = "windows")]
pub(super) fn uninstall_service(system: bool, purge: bool, keep_data: bool) -> Result<()> {
check_no_system_flag_windows(system)?;
stop_and_remove_service(system)?;
println!("Freenet autostart uninstalled.");
if super::purge::should_purge(purge, keep_data)? {
super::purge::purge_data_dirs(false)?;
println!("All Freenet data, config, and logs removed.");
}
Ok(())
}
#[cfg(target_os = "windows")]
pub(super) fn service_status(system: bool) -> Result<()> {
check_no_system_flag_windows(system)?;
let hkcu = winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER);
let registered = hkcu
.open_subkey(r"Software\Microsoft\Windows\CurrentVersion\Run")
.ok()
.and_then(|k| k.get_value::<String, _>("Freenet").ok())
.is_some();
if registered {
println!("Freenet autostart is registered.");
let running = std::process::Command::new("tasklist")
.args(["/fi", "imagename eq freenet.exe", "/fo", "csv", "/nh"])
.output()
.map(|o| {
let stdout = String::from_utf8_lossy(&o.stdout);
stdout.contains("freenet.exe")
})
.unwrap_or(false);
if running {
println!("Freenet is currently running.");
} else {
println!("Freenet is not currently running.");
}
} else {
println!("Freenet autostart is not registered.");
std::process::exit(3);
}
Ok(())
}
#[cfg(target_os = "windows")]
pub(super) fn start_service(system: bool) -> Result<()> {
check_no_system_flag_windows(system)?;
let exe_path = std::env::current_exe().context("Failed to get current executable path")?;
use std::os::windows::process::CommandExt;
const DETACHED_PROCESS: u32 = 0x00000008;
const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
std::process::Command::new(&exe_path)
.args(["service", "run-wrapper"])
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.creation_flags(DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP)
.spawn()
.context("Failed to start Freenet")?;
println!("Freenet started.");
println!("Open http://127.0.0.1:7509/ in your browser to view your Freenet dashboard.");
Ok(())
}
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
pub(super) fn command_line_args(cmdline: &str) -> &str {
let trimmed = cmdline.trim_start();
if let Some(after_open_quote) = trimmed.strip_prefix('"') {
after_open_quote
.find('"')
.map_or("", |close| after_open_quote[close + 1..].trim_start())
} else {
trimmed
.find(char::is_whitespace)
.map_or("", |space| trimmed[space..].trim_start())
}
}
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum FreenetServiceProcess {
Wrapper,
Node,
}
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
pub(super) fn classify_freenet_process(cmdline: &str) -> Option<FreenetServiceProcess> {
let mut args = command_line_args(cmdline).split_whitespace();
match args.next()? {
"network" => Some(FreenetServiceProcess::Node),
"service" if args.next() == Some("run-wrapper") => Some(FreenetServiceProcess::Wrapper),
_ => None,
}
}
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
pub(super) fn parse_freenet_process_listing(stdout: &str) -> Vec<(u32, String)> {
stdout
.lines()
.filter_map(|line| {
let line = line.trim_end_matches('\r');
let (pid, cmdline) = line.split_once('\t').unwrap_or((line, ""));
Some((pid.trim().parse::<u32>().ok()?, cmdline.to_string()))
})
.collect()
}
#[cfg(target_os = "windows")]
fn list_freenet_processes() -> Option<Vec<(u32, String)>> {
let output = std::process::Command::new("powershell")
.args([
"-NoProfile",
"-NonInteractive",
"-Command",
"Get-CimInstance Win32_Process -Filter \"Name='freenet.exe'\" | \
ForEach-Object { \"$($_.ProcessId)`t$($_.CommandLine)\" }",
])
.output()
.ok()?;
if !output.status.success() {
return None;
}
Some(parse_freenet_process_listing(&String::from_utf8_lossy(
&output.stdout,
)))
}
#[cfg(target_os = "windows")]
pub(super) fn taskkill_pid(pid: u32) -> bool {
std::process::Command::new("taskkill")
.args(["/f", "/pid", &pid.to_string()])
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.is_ok_and(|status| status.success())
}
#[cfg(target_os = "windows")]
pub(super) fn kill_freenet_processes_matching(kind: FreenetServiceProcess) -> usize {
let our_pid = std::process::id();
let Some(processes) = list_freenet_processes() else {
return 0;
};
let mut killed = 0;
for (pid, cmdline) in processes {
if pid != our_pid && classify_freenet_process(&cmdline) == Some(kind) && taskkill_pid(pid) {
killed += 1;
}
}
killed
}
#[cfg(target_os = "windows")]
pub(crate) fn kill_freenet_service_processes() -> usize {
let mut killed = kill_freenet_processes_matching(FreenetServiceProcess::Wrapper);
for _ in 0..3 {
let nodes = kill_freenet_processes_matching(FreenetServiceProcess::Node);
killed += nodes;
if nodes == 0 {
break;
}
std::thread::sleep(std::time::Duration::from_millis(200));
}
killed
}
#[cfg(target_os = "windows")]
pub(super) fn stop_service(system: bool) -> Result<()> {
check_no_system_flag_windows(system)?;
if kill_freenet_service_processes() > 0 {
println!("Freenet stopped.");
Ok(())
} else {
anyhow::bail!("Failed to stop Freenet. It may not be running.")
}
}
#[cfg(target_os = "windows")]
pub(super) fn restart_service(system: bool) -> Result<()> {
check_no_system_flag_windows(system)?;
drop(stop_service(false));
std::thread::sleep(std::time::Duration::from_secs(2));
start_service(false)
}
#[cfg(target_os = "windows")]
pub(super) fn service_logs(error_only: bool) -> Result<()> {
use freenet::tracing::tracer::get_log_dir;
use std::time::Duration;
let log_dir = get_log_dir().context(
"Could not determine log directory. \
Ensure Freenet has been run at least once via 'freenet service run-wrapper'.",
)?;
let base_name = if error_only {
"freenet.error"
} else {
"freenet"
};
let wrapper_log = find_latest_log_file(&log_dir, "freenet-wrapper");
let mut current_log = match find_latest_log_file(&log_dir, base_name) {
Some(log_path) => {
println!("Log file: {}", log_path.display());
if let Some(ref wl) = wrapper_log {
println!("Wrapper log: {}", wl.display());
}
log_path
}
None => {
if let Some(ref wl) = wrapper_log {
println!("No node logs found, showing wrapper log:");
let status = std::process::Command::new("powershell")
.args([
"-Command",
&format!("Get-Content -Path '{}' -Tail 50 -Wait", wl.display()),
])
.status()
.context("Failed to open wrapper log")?;
std::process::exit(status.code().unwrap_or(1));
} else {
anyhow::bail!(
"No log files found in {}.\n\
Ensure Freenet has been run at least once.",
log_dir.display()
);
}
}
};
println!("Press Ctrl+C to stop.\n");
loop {
let mut child = std::process::Command::new("powershell")
.args([
"-Command",
&format!(
"Get-Content -Path '{}' -Tail 50 -Wait",
current_log.display()
),
])
.spawn()
.context("Failed to spawn PowerShell for log tailing")?;
loop {
match child.try_wait() {
Ok(Some(status)) => {
if !status.success() {
drop(
std::process::Command::new("notepad")
.arg(¤t_log)
.spawn(),
);
}
std::process::exit(status.code().unwrap_or(1));
}
Ok(None) => {}
Err(e) => {
drop(child.kill());
drop(child.wait());
anyhow::bail!("Error waiting on PowerShell process: {e}");
}
}
std::thread::sleep(Duration::from_secs(5));
if let Some(newer_log) = find_latest_log_file(&log_dir, base_name) {
if newer_log != current_log {
println!("\n--- Log rotated to: {} ---\n", newer_log.display());
drop(child.kill());
drop(child.wait());
current_log = newer_log;
break;
}
}
}
}
}