feat: forward-by-default, auto recursive mode, Linux install fixes #27
10
install.sh
10
install.sh
@@ -70,8 +70,10 @@ echo ""
|
|||||||
echo " \033[38;2;107;124;78mInstalled:\033[0m $INSTALL_DIR/numa ($TAG)"
|
echo " \033[38;2;107;124;78mInstalled:\033[0m $INSTALL_DIR/numa ($TAG)"
|
||||||
echo ""
|
echo ""
|
||||||
echo " Get started:"
|
echo " Get started:"
|
||||||
echo " sudo numa # start the DNS server"
|
echo " sudo numa install # install service + set as system DNS"
|
||||||
echo " sudo numa install # set as system DNS"
|
echo " open http://localhost:5380 # dashboard"
|
||||||
echo " sudo numa service start # run as persistent service"
|
echo ""
|
||||||
echo " open http://localhost:5380 # dashboard"
|
echo " Other commands:"
|
||||||
|
echo " sudo numa # run in foreground (no service)"
|
||||||
|
echo " sudo numa uninstall # restore original DNS"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -882,6 +882,9 @@ async function refresh() {
|
|||||||
document.getElementById('footerUpstream').textContent = stats.upstream || '';
|
document.getElementById('footerUpstream').textContent = stats.upstream || '';
|
||||||
document.getElementById('footerConfig').textContent = stats.config_path || '';
|
document.getElementById('footerConfig').textContent = stats.config_path || '';
|
||||||
document.getElementById('footerData').textContent = stats.data_dir || '';
|
document.getElementById('footerData').textContent = stats.data_dir || '';
|
||||||
|
const modeEl = document.getElementById('footerMode');
|
||||||
|
modeEl.textContent = stats.mode || '—';
|
||||||
|
modeEl.style.color = stats.mode === 'recursive' ? 'var(--emerald)' : 'var(--amber)';
|
||||||
document.getElementById('footerDnssec').textContent = stats.dnssec ? 'on' : 'off';
|
document.getElementById('footerDnssec').textContent = stats.dnssec ? 'on' : 'off';
|
||||||
document.getElementById('footerDnssec').style.color = stats.dnssec ? 'var(--emerald)' : 'var(--text-dim)';
|
document.getElementById('footerDnssec').style.color = stats.dnssec ? 'var(--emerald)' : 'var(--text-dim)';
|
||||||
document.getElementById('footerSrtt').textContent = stats.srtt ? 'on' : 'off';
|
document.getElementById('footerSrtt').textContent = stats.srtt ? 'on' : 'off';
|
||||||
@@ -1236,6 +1239,7 @@ setInterval(refresh, 2000);
|
|||||||
Config: <span id="footerConfig" style="user-select:all;color:var(--emerald);"></span>
|
Config: <span id="footerConfig" style="user-select:all;color:var(--emerald);"></span>
|
||||||
· Data: <span id="footerData" style="user-select:all;color:var(--emerald);"></span>
|
· Data: <span id="footerData" style="user-select:all;color:var(--emerald);"></span>
|
||||||
· Upstream: <span id="footerUpstream" style="user-select:all;color:var(--emerald);"></span>
|
· Upstream: <span id="footerUpstream" style="user-select:all;color:var(--emerald);"></span>
|
||||||
|
· Mode: <span id="footerMode" style="color:var(--text-dim);">—</span>
|
||||||
· DNSSEC: <span id="footerDnssec" style="color:var(--text-dim);">—</span>
|
· DNSSEC: <span id="footerDnssec" style="color:var(--text-dim);">—</span>
|
||||||
· SRTT: <span id="footerSrtt" style="color:var(--text-dim);">—</span>
|
· SRTT: <span id="footerSrtt" style="color:var(--text-dim);">—</span>
|
||||||
· Logs: <span style="user-select:all;color:var(--emerald);">macOS: /usr/local/var/log/numa.log · Linux: journalctl -u numa -f</span>
|
· Logs: <span style="user-select:all;color:var(--emerald);">macOS: /usr/local/var/log/numa.log · Linux: journalctl -u numa -f</span>
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ struct QueryLogResponse {
|
|||||||
struct StatsResponse {
|
struct StatsResponse {
|
||||||
uptime_secs: u64,
|
uptime_secs: u64,
|
||||||
upstream: String,
|
upstream: String,
|
||||||
|
mode: String,
|
||||||
config_path: String,
|
config_path: String,
|
||||||
data_dir: String,
|
data_dir: String,
|
||||||
dnssec: bool,
|
dnssec: bool,
|
||||||
@@ -486,6 +487,7 @@ async fn stats(State(ctx): State<Arc<ServerCtx>>) -> Json<StatsResponse> {
|
|||||||
Json(StatsResponse {
|
Json(StatsResponse {
|
||||||
uptime_secs: snap.uptime_secs,
|
uptime_secs: snap.uptime_secs,
|
||||||
upstream,
|
upstream,
|
||||||
|
mode: ctx.upstream_mode.as_str().to_string(),
|
||||||
config_path: ctx.config_path.clone(),
|
config_path: ctx.config_path.clone(),
|
||||||
data_dir: ctx.data_dir.to_string_lossy().to_string(),
|
data_dir: ctx.data_dir.to_string_lossy().to_string(),
|
||||||
dnssec: ctx.dnssec_enabled,
|
dnssec: ctx.dnssec_enabled,
|
||||||
|
|||||||
@@ -67,10 +67,21 @@ fn default_api_port() -> u16 {
|
|||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum UpstreamMode {
|
pub enum UpstreamMode {
|
||||||
#[default]
|
#[default]
|
||||||
|
Auto,
|
||||||
Forward,
|
Forward,
|
||||||
Recursive,
|
Recursive,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl UpstreamMode {
|
||||||
|
pub fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
UpstreamMode::Auto => "auto",
|
||||||
|
UpstreamMode::Forward => "forward",
|
||||||
|
UpstreamMode::Recursive => "recursive",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct UpstreamConfig {
|
pub struct UpstreamConfig {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|||||||
92
src/main.rs
92
src/main.rs
@@ -17,8 +17,7 @@ use numa::query_log::QueryLog;
|
|||||||
use numa::service_store::ServiceStore;
|
use numa::service_store::ServiceStore;
|
||||||
use numa::stats::ServerStats;
|
use numa::stats::ServerStats;
|
||||||
use numa::system_dns::{
|
use numa::system_dns::{
|
||||||
discover_system_dns, install_service, install_system_dns, restart_service, service_status,
|
discover_system_dns, install_service, restart_service, service_status, uninstall_service,
|
||||||
uninstall_service, uninstall_system_dns,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@@ -31,12 +30,12 @@ async fn main() -> numa::Result<()> {
|
|||||||
let arg1 = std::env::args().nth(1).unwrap_or_default();
|
let arg1 = std::env::args().nth(1).unwrap_or_default();
|
||||||
match arg1.as_str() {
|
match arg1.as_str() {
|
||||||
"install" => {
|
"install" => {
|
||||||
eprintln!("\x1b[1;38;2;192;98;58mNuma\x1b[0m — configuring system DNS\n");
|
eprintln!("\x1b[1;38;2;192;98;58mNuma\x1b[0m — installing\n");
|
||||||
return install_system_dns().map_err(|e| e.into());
|
return install_service().map_err(|e| e.into());
|
||||||
}
|
}
|
||||||
"uninstall" => {
|
"uninstall" => {
|
||||||
eprintln!("\x1b[1;38;2;192;98;58mNuma\x1b[0m — restoring system DNS\n");
|
eprintln!("\x1b[1;38;2;192;98;58mNuma\x1b[0m — uninstalling\n");
|
||||||
return uninstall_system_dns().map_err(|e| e.into());
|
return uninstall_service().map_err(|e| e.into());
|
||||||
}
|
}
|
||||||
"service" => {
|
"service" => {
|
||||||
let sub = std::env::args().nth(2).unwrap_or_default();
|
let sub = std::env::args().nth(2).unwrap_or_default();
|
||||||
@@ -107,32 +106,63 @@ async fn main() -> numa::Result<()> {
|
|||||||
// Discover system DNS in a single pass (upstream + forwarding rules)
|
// Discover system DNS in a single pass (upstream + forwarding rules)
|
||||||
let system_dns = discover_system_dns();
|
let system_dns = discover_system_dns();
|
||||||
|
|
||||||
let upstream_addr = if config.upstream.address.is_empty() {
|
let root_hints = numa::recursive::parse_root_hints(&config.upstream.root_hints);
|
||||||
system_dns
|
|
||||||
.default_upstream
|
|
||||||
.or_else(numa::system_dns::detect_dhcp_dns)
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
info!("could not detect system DNS, falling back to Quad9 DoH");
|
|
||||||
"https://dns.quad9.net/dns-query".to_string()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
config.upstream.address.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let upstream: Upstream = if upstream_addr.starts_with("https://") {
|
// Resolve upstream mode + address in one block
|
||||||
let client = reqwest::Client::builder()
|
let resolved_mode;
|
||||||
.use_rustls_tls()
|
let upstream_auto;
|
||||||
.build()
|
let (upstream, upstream_label) = if config.upstream.mode == numa::config::UpstreamMode::Auto {
|
||||||
.unwrap_or_default();
|
info!("auto mode: probing recursive resolution...");
|
||||||
Upstream::Doh {
|
if numa::recursive::probe_recursive(&root_hints).await {
|
||||||
url: upstream_addr,
|
info!("recursive probe succeeded — self-sovereign mode");
|
||||||
client,
|
resolved_mode = numa::config::UpstreamMode::Recursive;
|
||||||
|
upstream_auto = false;
|
||||||
|
let dummy_upstream = Upstream::Udp("0.0.0.0:0".parse().unwrap());
|
||||||
|
(dummy_upstream, "recursive (root hints)".to_string())
|
||||||
|
} else {
|
||||||
|
log::warn!("recursive probe failed — falling back to Quad9 DoH");
|
||||||
|
resolved_mode = numa::config::UpstreamMode::Forward;
|
||||||
|
upstream_auto = false;
|
||||||
|
let client = reqwest::Client::builder()
|
||||||
|
.use_rustls_tls()
|
||||||
|
.build()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let url = "https://dns.quad9.net/dns-query".to_string();
|
||||||
|
let label = url.clone();
|
||||||
|
(Upstream::Doh { url, client }, label)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let addr: SocketAddr = format!("{}:{}", upstream_addr, config.upstream.port).parse()?;
|
resolved_mode = config.upstream.mode;
|
||||||
Upstream::Udp(addr)
|
upstream_auto = config.upstream.address.is_empty();
|
||||||
|
|
||||||
|
let upstream_addr = if config.upstream.address.is_empty() {
|
||||||
|
system_dns
|
||||||
|
.default_upstream
|
||||||
|
.or_else(numa::system_dns::detect_dhcp_dns)
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
info!("could not detect system DNS, falling back to Quad9 DoH");
|
||||||
|
"https://dns.quad9.net/dns-query".to_string()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
config.upstream.address.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let upstream: Upstream = if upstream_addr.starts_with("https://") {
|
||||||
|
let client = reqwest::Client::builder()
|
||||||
|
.use_rustls_tls()
|
||||||
|
.build()
|
||||||
|
.unwrap_or_default();
|
||||||
|
Upstream::Doh {
|
||||||
|
url: upstream_addr,
|
||||||
|
client,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let addr: SocketAddr = format!("{}:{}", upstream_addr, config.upstream.port).parse()?;
|
||||||
|
Upstream::Udp(addr)
|
||||||
|
};
|
||||||
|
let label = upstream.to_string();
|
||||||
|
(upstream, label)
|
||||||
};
|
};
|
||||||
let upstream_label = upstream.to_string();
|
|
||||||
let api_port = config.server.api_port;
|
let api_port = config.server.api_port;
|
||||||
|
|
||||||
let mut blocklist = BlocklistStore::new();
|
let mut blocklist = BlocklistStore::new();
|
||||||
@@ -183,7 +213,7 @@ async fn main() -> numa::Result<()> {
|
|||||||
lan_peers: Mutex::new(numa::lan::PeerStore::new(config.lan.peer_timeout_secs)),
|
lan_peers: Mutex::new(numa::lan::PeerStore::new(config.lan.peer_timeout_secs)),
|
||||||
forwarding_rules,
|
forwarding_rules,
|
||||||
upstream: Mutex::new(upstream),
|
upstream: Mutex::new(upstream),
|
||||||
upstream_auto: config.upstream.address.is_empty(),
|
upstream_auto,
|
||||||
upstream_port: config.upstream.port,
|
upstream_port: config.upstream.port,
|
||||||
lan_ip: Mutex::new(numa::lan::detect_lan_ip().unwrap_or(std::net::Ipv4Addr::LOCALHOST)),
|
lan_ip: Mutex::new(numa::lan::detect_lan_ip().unwrap_or(std::net::Ipv4Addr::LOCALHOST)),
|
||||||
timeout: Duration::from_millis(config.upstream.timeout_ms),
|
timeout: Duration::from_millis(config.upstream.timeout_ms),
|
||||||
@@ -199,8 +229,8 @@ async fn main() -> numa::Result<()> {
|
|||||||
config_dir: numa::config_dir(),
|
config_dir: numa::config_dir(),
|
||||||
data_dir: numa::data_dir(),
|
data_dir: numa::data_dir(),
|
||||||
tls_config: initial_tls,
|
tls_config: initial_tls,
|
||||||
upstream_mode: config.upstream.mode,
|
upstream_mode: resolved_mode,
|
||||||
root_hints: numa::recursive::parse_root_hints(&config.upstream.root_hints),
|
root_hints,
|
||||||
srtt: std::sync::RwLock::new(numa::srtt::SrttCache::new(config.upstream.srtt)),
|
srtt: std::sync::RwLock::new(numa::srtt::SrttCache::new(config.upstream.srtt)),
|
||||||
inflight: std::sync::Mutex::new(std::collections::HashMap::new()),
|
inflight: std::sync::Mutex::new(std::collections::HashMap::new()),
|
||||||
dnssec_enabled: config.dnssec.enabled,
|
dnssec_enabled: config.dnssec.enabled,
|
||||||
|
|||||||
@@ -65,6 +65,20 @@ pub async fn probe_udp(root_hints: &[SocketAddr]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Probe whether recursive resolution works by querying a root server.
|
||||||
|
pub async fn probe_recursive(root_hints: &[SocketAddr]) -> bool {
|
||||||
|
let hint = match root_hints.first() {
|
||||||
|
Some(h) => *h,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
let mut probe = DnsPacket::query(next_id(), ".", QueryType::NS);
|
||||||
|
probe.header.recursion_desired = false;
|
||||||
|
match forward_udp(&probe, hint, Duration::from_secs(3)).await {
|
||||||
|
Ok(resp) => !resp.answers.is_empty() || !resp.authorities.is_empty(),
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn prime_tld_cache(
|
pub async fn prime_tld_cache(
|
||||||
cache: &RwLock<DnsCache>,
|
cache: &RwLock<DnsCache>,
|
||||||
root_hints: &[SocketAddr],
|
root_hints: &[SocketAddr],
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ use std::net::SocketAddr;
|
|||||||
|
|
||||||
use log::info;
|
use log::info;
|
||||||
|
|
||||||
|
fn is_loopback_or_stub(addr: &str) -> bool {
|
||||||
|
matches!(addr, "127.0.0.1" | "127.0.0.53" | "0.0.0.0" | "::1" | "")
|
||||||
|
}
|
||||||
|
|
||||||
/// A conditional forwarding rule: domains matching `suffix` are forwarded to `upstream`.
|
/// A conditional forwarding rule: domains matching `suffix` are forwarded to `upstream`.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ForwardingRule {
|
pub struct ForwardingRule {
|
||||||
@@ -102,11 +106,7 @@ fn discover_macos() -> SystemDnsInfo {
|
|||||||
if ns.parse::<std::net::Ipv4Addr>().is_ok() {
|
if ns.parse::<std::net::Ipv4Addr>().is_ok() {
|
||||||
current_nameserver = Some(ns.clone());
|
current_nameserver = Some(ns.clone());
|
||||||
// Capture first non-supplemental, non-loopback nameserver as default upstream
|
// Capture first non-supplemental, non-loopback nameserver as default upstream
|
||||||
if !is_supplemental
|
if !is_supplemental && default_upstream.is_none() && !is_loopback_or_stub(&ns) {
|
||||||
&& default_upstream.is_none()
|
|
||||||
&& ns != "127.0.0.1"
|
|
||||||
&& ns != "0.0.0.0"
|
|
||||||
{
|
|
||||||
default_upstream = Some(ns);
|
default_upstream = Some(ns);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,7 +196,7 @@ fn read_upstream_from_file(path: &str) -> Option<String> {
|
|||||||
let line = line.trim();
|
let line = line.trim();
|
||||||
if line.starts_with("nameserver") {
|
if line.starts_with("nameserver") {
|
||||||
if let Some(ns) = line.split_whitespace().nth(1) {
|
if let Some(ns) = line.split_whitespace().nth(1) {
|
||||||
if ns != "127.0.0.1" && ns != "0.0.0.0" && ns != "::1" {
|
if !is_loopback_or_stub(ns) {
|
||||||
return Some(ns.to_string());
|
return Some(ns.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -236,10 +236,7 @@ fn detect_dhcp_dns_macos() -> Option<String> {
|
|||||||
// Take the first non-loopback DNS server
|
// Take the first non-loopback DNS server
|
||||||
for addr in inner.split(',') {
|
for addr in inner.split(',') {
|
||||||
let addr = addr.trim();
|
let addr = addr.trim();
|
||||||
if !addr.is_empty()
|
if !is_loopback_or_stub(addr) && addr.parse::<std::net::Ipv4Addr>().is_ok()
|
||||||
&& addr != "127.0.0.1"
|
|
||||||
&& addr != "0.0.0.0"
|
|
||||||
&& addr.parse::<std::net::Ipv4Addr>().is_ok()
|
|
||||||
{
|
{
|
||||||
log::info!("detected DHCP DNS: {}", addr);
|
log::info!("detected DHCP DNS: {}", addr);
|
||||||
return Some(addr.to_string());
|
return Some(addr.to_string());
|
||||||
@@ -278,7 +275,7 @@ fn discover_windows() -> SystemDnsInfo {
|
|||||||
if trimmed.contains("DNS Servers") || trimmed.contains("DNS-Server") {
|
if trimmed.contains("DNS Servers") || trimmed.contains("DNS-Server") {
|
||||||
if let Some(ip) = trimmed.split(':').next_back() {
|
if let Some(ip) = trimmed.split(':').next_back() {
|
||||||
let ip = ip.trim();
|
let ip = ip.trim();
|
||||||
if !ip.is_empty() && ip != "127.0.0.1" && ip != "::1" {
|
if !is_loopback_or_stub(ip) {
|
||||||
upstream = Some(ip.to_string());
|
upstream = Some(ip.to_string());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -316,43 +313,6 @@ pub fn match_forwarding_rule(domain: &str, rules: &[ForwardingRule]) -> Option<S
|
|||||||
|
|
||||||
// --- System DNS configuration (install/uninstall) ---
|
// --- System DNS configuration (install/uninstall) ---
|
||||||
|
|
||||||
/// Set the system DNS to 127.0.0.1 so all queries go through Numa.
|
|
||||||
/// Saves the original DNS settings for later restoration.
|
|
||||||
pub fn install_system_dns() -> Result<(), String> {
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
let result = install_macos();
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
let result = install_linux();
|
|
||||||
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
|
|
||||||
let result = Err("system DNS configuration not supported on this OS".to_string());
|
|
||||||
|
|
||||||
if result.is_ok() {
|
|
||||||
if let Err(e) = trust_ca() {
|
|
||||||
eprintln!(" warning: could not trust CA: {}", e);
|
|
||||||
eprintln!(" HTTPS proxy will work but browsers will show certificate warnings.\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Restore the original system DNS settings saved during install.
|
|
||||||
pub fn uninstall_system_dns() -> Result<(), String> {
|
|
||||||
let _ = untrust_ca();
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
{
|
|
||||||
uninstall_macos()
|
|
||||||
}
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
{
|
|
||||||
uninstall_linux()
|
|
||||||
}
|
|
||||||
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
|
|
||||||
{
|
|
||||||
Err("system DNS configuration not supported on this OS".to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- macOS implementation ---
|
// --- macOS implementation ---
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
@@ -500,21 +460,25 @@ const SYSTEMD_UNIT: &str = "/etc/systemd/system/numa.service";
|
|||||||
/// Install Numa as a system service that starts on boot and auto-restarts.
|
/// Install Numa as a system service that starts on boot and auto-restarts.
|
||||||
pub fn install_service() -> Result<(), String> {
|
pub fn install_service() -> Result<(), String> {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
let result = install_service_macos();
|
||||||
install_service_macos()
|
|
||||||
}
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
let result = install_service_linux();
|
||||||
install_service_linux()
|
|
||||||
}
|
|
||||||
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
|
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
|
||||||
{
|
let result = Err::<(), String>("service installation not supported on this OS".to_string());
|
||||||
Err("service installation not supported on this OS".to_string())
|
|
||||||
|
if result.is_ok() {
|
||||||
|
if let Err(e) = trust_ca() {
|
||||||
|
eprintln!(" warning: could not trust CA: {}", e);
|
||||||
|
eprintln!(" HTTPS proxy will work but browsers will show certificate warnings.\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Uninstall the Numa system service.
|
/// Uninstall the Numa system service.
|
||||||
pub fn uninstall_service() -> Result<(), String> {
|
pub fn uninstall_service() -> Result<(), String> {
|
||||||
|
let _ = untrust_ca();
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
uninstall_service_macos()
|
uninstall_service_macos()
|
||||||
@@ -609,6 +573,11 @@ fn install_service_macos() -> Result<(), String> {
|
|||||||
std::fs::write(PLIST_DEST, plist)
|
std::fs::write(PLIST_DEST, plist)
|
||||||
.map_err(|e| format!("failed to write {}: {}", PLIST_DEST, e))?;
|
.map_err(|e| format!("failed to write {}: {}", PLIST_DEST, e))?;
|
||||||
|
|
||||||
|
// Configure system DNS before starting service
|
||||||
|
if let Err(e) = install_macos() {
|
||||||
|
eprintln!(" warning: failed to configure system DNS: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
// Load the service
|
// Load the service
|
||||||
let status = std::process::Command::new("launchctl")
|
let status = std::process::Command::new("launchctl")
|
||||||
.args(["load", "-w", PLIST_DEST])
|
.args(["load", "-w", PLIST_DEST])
|
||||||
@@ -619,11 +588,7 @@ fn install_service_macos() -> Result<(), String> {
|
|||||||
return Err("launchctl load failed".to_string());
|
return Err("launchctl load failed".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set system DNS to 127.0.0.1 now that the service is running
|
|
||||||
eprintln!(" Service installed and started.");
|
eprintln!(" Service installed and started.");
|
||||||
if let Err(e) = install_macos() {
|
|
||||||
eprintln!(" warning: failed to configure system DNS: {}", e);
|
|
||||||
}
|
|
||||||
eprintln!(" Numa will auto-start on boot and restart if killed.");
|
eprintln!(" Numa will auto-start on boot and restart if killed.");
|
||||||
eprintln!(" Logs: /usr/local/var/log/numa.log");
|
eprintln!(" Logs: /usr/local/var/log/numa.log");
|
||||||
eprintln!(" Run 'sudo numa service stop' to fully uninstall.\n");
|
eprintln!(" Run 'sudo numa service stop' to fully uninstall.\n");
|
||||||
@@ -708,8 +673,11 @@ fn install_linux() -> Result<(), String> {
|
|||||||
.map_err(|e| format!("failed to create {}: {}", resolved_dir.display(), e))?;
|
.map_err(|e| format!("failed to create {}: {}", resolved_dir.display(), e))?;
|
||||||
|
|
||||||
let drop_in = resolved_dir.join("numa.conf");
|
let drop_in = resolved_dir.join("numa.conf");
|
||||||
std::fs::write(&drop_in, "[Resolve]\nDNS=127.0.0.1\nDomains=~.\n")
|
std::fs::write(
|
||||||
.map_err(|e| format!("failed to write {}: {}", drop_in.display(), e))?;
|
&drop_in,
|
||||||
|
"[Resolve]\nDNS=127.0.0.1\nDomains=~.\nDNSStubListener=no\n",
|
||||||
|
)
|
||||||
|
.map_err(|e| format!("failed to write {}: {}", drop_in.display(), e))?;
|
||||||
|
|
||||||
let _ = run_systemctl(&["restart", "systemd-resolved"]);
|
let _ = run_systemctl(&["restart", "systemd-resolved"]);
|
||||||
eprintln!(" systemd-resolved detected.");
|
eprintln!(" systemd-resolved detected.");
|
||||||
@@ -802,14 +770,15 @@ fn install_service_linux() -> Result<(), String> {
|
|||||||
|
|
||||||
run_systemctl(&["daemon-reload"])?;
|
run_systemctl(&["daemon-reload"])?;
|
||||||
run_systemctl(&["enable", "numa"])?;
|
run_systemctl(&["enable", "numa"])?;
|
||||||
run_systemctl(&["start", "numa"])?;
|
|
||||||
|
|
||||||
eprintln!(" Service installed and started.");
|
// Configure system DNS before starting numa so resolved releases port 53 first
|
||||||
|
|
||||||
// Set system DNS now that the service is running
|
|
||||||
if let Err(e) = install_linux() {
|
if let Err(e) = install_linux() {
|
||||||
eprintln!(" warning: failed to configure system DNS: {}", e);
|
eprintln!(" warning: failed to configure system DNS: {}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
run_systemctl(&["start", "numa"])?;
|
||||||
|
|
||||||
|
eprintln!(" Service installed and started.");
|
||||||
eprintln!(" Numa will auto-start on boot and restart if killed.");
|
eprintln!(" Numa will auto-start on boot and restart if killed.");
|
||||||
eprintln!(" Logs: journalctl -u numa -f");
|
eprintln!(" Logs: journalctl -u numa -f");
|
||||||
eprintln!(" Run 'sudo numa service stop' to fully uninstall.\n");
|
eprintln!(" Run 'sudo numa service stop' to fully uninstall.\n");
|
||||||
|
|||||||
Reference in New Issue
Block a user