add auto-detect upstream, install script, release workflow
- Default upstream auto-detected from system resolver (scutil/resolv.conf) instead of hardcoding Google 8.8.8.8. Falls back to Quad9 (9.9.9.9). - Single scutil --dns pass for both upstream detection and forwarding rules - Linux: reads backup resolv.conf if current only has loopback - Service start/stop now couples DNS config (install on start, uninstall on stop) - Install script for one-line binary install from GitHub Releases - GitHub Actions release workflow: builds for macOS/Linux x86_64/aarch64 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
78
.github/workflows/release.yml
vendored
Normal file
78
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- target: x86_64-apple-darwin
|
||||||
|
os: macos-latest
|
||||||
|
name: numa-macos-x86_64
|
||||||
|
- target: aarch64-apple-darwin
|
||||||
|
os: macos-latest
|
||||||
|
name: numa-macos-aarch64
|
||||||
|
- target: x86_64-unknown-linux-gnu
|
||||||
|
os: ubuntu-latest
|
||||||
|
name: numa-linux-x86_64
|
||||||
|
- target: aarch64-unknown-linux-gnu
|
||||||
|
os: ubuntu-latest
|
||||||
|
name: numa-linux-aarch64
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
targets: ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Install cross-compilation tools
|
||||||
|
if: matrix.target == 'aarch64-unknown-linux-gnu'
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y gcc-aarch64-linux-gnu
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cargo build --release --target ${{ matrix.target }}
|
||||||
|
env:
|
||||||
|
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
|
||||||
|
|
||||||
|
- name: Package
|
||||||
|
run: |
|
||||||
|
cd target/${{ matrix.target }}/release
|
||||||
|
tar czf ../../../${{ matrix.name }}.tar.gz numa
|
||||||
|
cd ../../..
|
||||||
|
sha256sum ${{ matrix.name }}.tar.gz > ${{ matrix.name }}.tar.gz.sha256
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ matrix.name }}
|
||||||
|
path: |
|
||||||
|
${{ matrix.name }}.tar.gz
|
||||||
|
${{ matrix.name }}.tar.gz.sha256
|
||||||
|
|
||||||
|
release:
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
generate_release_notes: true
|
||||||
|
files: |
|
||||||
|
*.tar.gz
|
||||||
|
*.sha256
|
||||||
@@ -16,5 +16,10 @@
|
|||||||
<string>/usr/local/var/log/numa.log</string>
|
<string>/usr/local/var/log/numa.log</string>
|
||||||
<key>StandardErrorPath</key>
|
<key>StandardErrorPath</key>
|
||||||
<string>/usr/local/var/log/numa.log</string>
|
<string>/usr/local/var/log/numa.log</string>
|
||||||
|
<key>EnvironmentVariables</key>
|
||||||
|
<dict>
|
||||||
|
<key>RUST_LOG</key>
|
||||||
|
<string>info</string>
|
||||||
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
71
install.sh
Executable file
71
install.sh
Executable file
@@ -0,0 +1,71 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Numa installer — detects OS/arch and downloads the latest release
|
||||||
|
# Usage: curl -sSL https://raw.githubusercontent.com/razvandimescu/numa/main/install.sh | sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
REPO="razvandimescu/numa"
|
||||||
|
INSTALL_DIR="/usr/local/bin"
|
||||||
|
|
||||||
|
# Detect OS
|
||||||
|
OS="$(uname -s)"
|
||||||
|
case "$OS" in
|
||||||
|
Darwin) OS_NAME="macos" ;;
|
||||||
|
Linux) OS_NAME="linux" ;;
|
||||||
|
*) echo "Unsupported OS: $OS"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Detect architecture
|
||||||
|
ARCH="$(uname -m)"
|
||||||
|
case "$ARCH" in
|
||||||
|
x86_64|amd64) ARCH_NAME="x86_64" ;;
|
||||||
|
arm64|aarch64) ARCH_NAME="aarch64" ;;
|
||||||
|
*) echo "Unsupported architecture: $ARCH"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
ASSET="numa-${OS_NAME}-${ARCH_NAME}.tar.gz"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo " \033[1;38;2;192;98;58mNuma\033[0m installer"
|
||||||
|
echo ""
|
||||||
|
echo " OS: $OS_NAME"
|
||||||
|
echo " Arch: $ARCH_NAME"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Get latest release tag
|
||||||
|
echo " Fetching latest release..."
|
||||||
|
TAG=$(curl -sSL "https://api.github.com/repos/${REPO}/releases/latest" | grep '"tag_name"' | head -1 | sed 's/.*"tag_name": *"\([^"]*\)".*/\1/')
|
||||||
|
|
||||||
|
if [ -z "$TAG" ]; then
|
||||||
|
echo " Error: could not find latest release."
|
||||||
|
echo " Check https://github.com/${REPO}/releases"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
URL="https://github.com/${REPO}/releases/download/${TAG}/${ASSET}"
|
||||||
|
echo " Downloading ${TAG}..."
|
||||||
|
|
||||||
|
# Download and extract
|
||||||
|
TMP=$(mktemp -d)
|
||||||
|
curl -sSL "$URL" -o "$TMP/$ASSET"
|
||||||
|
tar xzf "$TMP/$ASSET" -C "$TMP"
|
||||||
|
|
||||||
|
# Install
|
||||||
|
if [ -w "$INSTALL_DIR" ]; then
|
||||||
|
mv "$TMP/numa" "$INSTALL_DIR/numa"
|
||||||
|
else
|
||||||
|
echo " Installing to $INSTALL_DIR (requires sudo)..."
|
||||||
|
sudo mv "$TMP/numa" "$INSTALL_DIR/numa"
|
||||||
|
fi
|
||||||
|
|
||||||
|
chmod +x "$INSTALL_DIR/numa"
|
||||||
|
rm -rf "$TMP"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo " \033[38;2;107;124;78mInstalled:\033[0m $INSTALL_DIR/numa ($TAG)"
|
||||||
|
echo ""
|
||||||
|
echo " Get started:"
|
||||||
|
echo " sudo numa # start the DNS server"
|
||||||
|
echo " sudo numa install # set as system DNS"
|
||||||
|
echo " sudo numa service start # run as persistent service"
|
||||||
|
echo " open http://localhost:5380 # dashboard"
|
||||||
|
echo ""
|
||||||
@@ -2,10 +2,11 @@
|
|||||||
bind_addr = "0.0.0.0:53"
|
bind_addr = "0.0.0.0:53"
|
||||||
api_port = 5380
|
api_port = 5380
|
||||||
|
|
||||||
[upstream]
|
# [upstream]
|
||||||
address = "8.8.8.8"
|
# address = "" # auto-detect from system resolver (default)
|
||||||
port = 53
|
# address = "9.9.9.9" # or set explicitly
|
||||||
timeout_ms = 3000
|
# port = 53
|
||||||
|
# timeout_ms = 3000
|
||||||
|
|
||||||
[cache]
|
[cache]
|
||||||
max_entries = 10000
|
max_entries = 10000
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ impl Default for UpstreamConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn default_upstream_addr() -> String {
|
fn default_upstream_addr() -> String {
|
||||||
"8.8.8.8".to_string()
|
String::new() // empty = auto-detect from system resolver
|
||||||
}
|
}
|
||||||
fn default_upstream_port() -> u16 {
|
fn default_upstream_port() -> u16 {
|
||||||
53
|
53
|
||||||
|
|||||||
21
src/main.rs
21
src/main.rs
@@ -14,8 +14,8 @@ use numa::override_store::OverrideStore;
|
|||||||
use numa::query_log::QueryLog;
|
use numa::query_log::QueryLog;
|
||||||
use numa::stats::ServerStats;
|
use numa::stats::ServerStats;
|
||||||
use numa::system_dns::{
|
use numa::system_dns::{
|
||||||
discover_forwarding_rules, install_service, install_system_dns, service_status,
|
discover_system_dns, install_service, install_system_dns, service_status, uninstall_service,
|
||||||
uninstall_service, uninstall_system_dns,
|
uninstall_system_dns,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@@ -75,8 +75,18 @@ async fn main() -> numa::Result<()> {
|
|||||||
};
|
};
|
||||||
let config = load_config(&config_path)?;
|
let config = load_config(&config_path)?;
|
||||||
|
|
||||||
let upstream: SocketAddr =
|
// Discover system DNS in a single pass (upstream + forwarding rules)
|
||||||
format!("{}:{}", config.upstream.address, config.upstream.port).parse()?;
|
let system_dns = discover_system_dns();
|
||||||
|
|
||||||
|
let upstream_addr = if config.upstream.address.is_empty() {
|
||||||
|
system_dns.default_upstream.unwrap_or_else(|| {
|
||||||
|
info!("could not detect system DNS, falling back to 9.9.9.9 (Quad9)");
|
||||||
|
"9.9.9.9".to_string()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
config.upstream.address.clone()
|
||||||
|
};
|
||||||
|
let upstream: SocketAddr = format!("{}:{}", upstream_addr, config.upstream.port).parse()?;
|
||||||
let api_port = config.server.api_port;
|
let api_port = config.server.api_port;
|
||||||
|
|
||||||
let mut blocklist = BlocklistStore::new();
|
let mut blocklist = BlocklistStore::new();
|
||||||
@@ -87,8 +97,7 @@ async fn main() -> numa::Result<()> {
|
|||||||
blocklist.set_enabled(false);
|
blocklist.set_enabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-discover conditional forwarding rules from OS (Tailscale, VPN, etc.)
|
let forwarding_rules = system_dns.forwarding_rules;
|
||||||
let forwarding_rules = discover_forwarding_rules();
|
|
||||||
|
|
||||||
let ctx = Arc::new(ServerCtx {
|
let ctx = Arc::new(ServerCtx {
|
||||||
socket: UdpSocket::bind(&config.server.bind_addr).await?,
|
socket: UdpSocket::bind(&config.server.bind_addr).await?,
|
||||||
|
|||||||
@@ -70,7 +70,11 @@ impl DnsPacket {
|
|||||||
pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result<()> {
|
pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result<()> {
|
||||||
// Filter out UNKNOWN records (e.g. EDNS OPT) that we can't re-serialize
|
// Filter out UNKNOWN records (e.g. EDNS OPT) that we can't re-serialize
|
||||||
let answers: Vec<_> = self.answers.iter().filter(|r| !r.is_unknown()).collect();
|
let answers: Vec<_> = self.answers.iter().filter(|r| !r.is_unknown()).collect();
|
||||||
let authorities: Vec<_> = self.authorities.iter().filter(|r| !r.is_unknown()).collect();
|
let authorities: Vec<_> = self
|
||||||
|
.authorities
|
||||||
|
.iter()
|
||||||
|
.filter(|r| !r.is_unknown())
|
||||||
|
.collect();
|
||||||
let resources: Vec<_> = self.resources.iter().filter(|r| !r.is_unknown()).collect();
|
let resources: Vec<_> = self.resources.iter().filter(|r| !r.is_unknown()).collect();
|
||||||
|
|
||||||
let mut header = self.header.clone();
|
let mut header = self.header.clone();
|
||||||
|
|||||||
@@ -10,38 +10,48 @@ pub struct ForwardingRule {
|
|||||||
pub upstream: SocketAddr,
|
pub upstream: SocketAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Discover system DNS forwarding rules from the OS.
|
/// Result of system DNS discovery — default upstream + conditional forwarding rules.
|
||||||
/// On macOS, parses `scutil --dns`. Returns rules sorted longest-suffix-first
|
pub struct SystemDnsInfo {
|
||||||
/// so more specific matches take priority.
|
pub default_upstream: Option<String>,
|
||||||
pub fn discover_forwarding_rules() -> Vec<ForwardingRule> {
|
pub forwarding_rules: Vec<ForwardingRule>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Discover system DNS configuration in a single pass.
|
||||||
|
/// On macOS: parses `scutil --dns` once for both the default upstream and forwarding rules.
|
||||||
|
/// On Linux: reads `/etc/resolv.conf` for upstream, no forwarding rules yet.
|
||||||
|
pub fn discover_system_dns() -> SystemDnsInfo {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
discover_macos()
|
discover_macos()
|
||||||
}
|
}
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
{
|
{
|
||||||
info!("system DNS auto-discovery not implemented for this OS");
|
SystemDnsInfo {
|
||||||
Vec::new()
|
default_upstream: detect_upstream_linux_or_backup(),
|
||||||
|
forwarding_rules: Vec::new(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
fn discover_macos() -> Vec<ForwardingRule> {
|
fn discover_macos() -> SystemDnsInfo {
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
|
|
||||||
let output = match std::process::Command::new("scutil").arg("--dns").output() {
|
let output = match std::process::Command::new("scutil").arg("--dns").output() {
|
||||||
Ok(o) => o,
|
Ok(o) => o,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("failed to run scutil --dns: {}", e);
|
warn!("failed to run scutil --dns: {}", e);
|
||||||
return Vec::new();
|
return SystemDnsInfo {
|
||||||
|
default_upstream: None,
|
||||||
|
forwarding_rules: Vec::new(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let text = String::from_utf8_lossy(&output.stdout);
|
let text = String::from_utf8_lossy(&output.stdout);
|
||||||
let mut rules = Vec::new();
|
let mut rules = Vec::new();
|
||||||
|
let mut default_upstream: Option<String> = None;
|
||||||
|
|
||||||
// Parse resolver blocks: look for blocks with both `domain` and `nameserver[0]`
|
|
||||||
// that have the `Supplemental` flag (conditional forwarding, not default)
|
|
||||||
let mut current_domain: Option<String> = None;
|
let mut current_domain: Option<String> = None;
|
||||||
let mut current_nameserver: Option<String> = None;
|
let mut current_nameserver: Option<String> = None;
|
||||||
let mut is_supplemental = false;
|
let mut is_supplemental = false;
|
||||||
@@ -50,7 +60,7 @@ fn discover_macos() -> Vec<ForwardingRule> {
|
|||||||
let line = line.trim();
|
let line = line.trim();
|
||||||
|
|
||||||
if line.starts_with("resolver #") {
|
if line.starts_with("resolver #") {
|
||||||
// Emit previous block if valid
|
// Emit previous supplemental block as forwarding rule
|
||||||
if let (Some(domain), Some(ns), true) = (
|
if let (Some(domain), Some(ns), true) = (
|
||||||
current_domain.take(),
|
current_domain.take(),
|
||||||
current_nameserver.take(),
|
current_nameserver.take(),
|
||||||
@@ -64,7 +74,6 @@ fn discover_macos() -> Vec<ForwardingRule> {
|
|||||||
current_nameserver = None;
|
current_nameserver = None;
|
||||||
is_supplemental = false;
|
is_supplemental = false;
|
||||||
} else if line.starts_with("domain") && line.contains(':') {
|
} else if line.starts_with("domain") && line.contains(':') {
|
||||||
// "domain : tailcee7cc.ts.net."
|
|
||||||
if let Some(val) = line.split(':').nth(1) {
|
if let Some(val) = line.split(':').nth(1) {
|
||||||
let domain = val.trim().trim_end_matches('.').to_lowercase();
|
let domain = val.trim().trim_end_matches('.').to_lowercase();
|
||||||
if !domain.is_empty()
|
if !domain.is_empty()
|
||||||
@@ -78,15 +87,21 @@ fn discover_macos() -> Vec<ForwardingRule> {
|
|||||||
} else if line.starts_with("nameserver[0]") && line.contains(':') {
|
} else if line.starts_with("nameserver[0]") && line.contains(':') {
|
||||||
if let Some(val) = line.split(':').nth(1) {
|
if let Some(val) = line.split(':').nth(1) {
|
||||||
let ns = val.trim().to_string();
|
let ns = val.trim().to_string();
|
||||||
// Only use IPv4 nameservers for now
|
|
||||||
if ns.parse::<std::net::Ipv4Addr>().is_ok() {
|
if ns.parse::<std::net::Ipv4Addr>().is_ok() {
|
||||||
current_nameserver = Some(ns);
|
current_nameserver = Some(ns.clone());
|
||||||
|
// Capture first non-supplemental, non-loopback nameserver as default upstream
|
||||||
|
if !is_supplemental
|
||||||
|
&& default_upstream.is_none()
|
||||||
|
&& ns != "127.0.0.1"
|
||||||
|
&& ns != "0.0.0.0"
|
||||||
|
{
|
||||||
|
default_upstream = Some(ns);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if line.starts_with("flags") && line.contains("Supplemental") {
|
} else if line.starts_with("flags") && line.contains("Supplemental") {
|
||||||
is_supplemental = true;
|
is_supplemental = true;
|
||||||
} else if line.starts_with("DNS configuration (for scoped") {
|
} else if line.starts_with("DNS configuration (for scoped") {
|
||||||
// Stop at scoped section — those are interface-specific, not conditional
|
|
||||||
if let (Some(domain), Some(ns), true) = (
|
if let (Some(domain), Some(ns), true) = (
|
||||||
current_domain.take(),
|
current_domain.take(),
|
||||||
current_nameserver.take(),
|
current_nameserver.take(),
|
||||||
@@ -116,12 +131,17 @@ fn discover_macos() -> Vec<ForwardingRule> {
|
|||||||
rule.suffix, rule.upstream
|
rule.suffix, rule.upstream
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if rules.is_empty() {
|
if rules.is_empty() {
|
||||||
debug!("no conditional forwarding rules discovered from scutil --dns");
|
debug!("no conditional forwarding rules discovered");
|
||||||
|
}
|
||||||
|
if let Some(ref ns) = default_upstream {
|
||||||
|
info!("detected system upstream: {}", ns);
|
||||||
}
|
}
|
||||||
|
|
||||||
rules
|
SystemDnsInfo {
|
||||||
|
default_upstream,
|
||||||
|
forwarding_rules: rules,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
@@ -134,6 +154,45 @@ fn make_rule(domain: &str, nameserver: &str) -> Option<ForwardingRule> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Detect upstream from /etc/resolv.conf, falling back to backup file if resolv.conf
|
||||||
|
/// only has loopback (meaning numa install already ran).
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
fn detect_upstream_linux_or_backup() -> Option<String> {
|
||||||
|
// Try /etc/resolv.conf first
|
||||||
|
if let Some(ns) = read_upstream_from_file("/etc/resolv.conf") {
|
||||||
|
info!("detected system upstream: {}", ns);
|
||||||
|
return Some(ns);
|
||||||
|
}
|
||||||
|
// If resolv.conf only has loopback, check the backup from `numa install`
|
||||||
|
let backup = {
|
||||||
|
let home = std::env::var("HOME")
|
||||||
|
.map(std::path::PathBuf::from)
|
||||||
|
.unwrap_or_else(|_| std::path::PathBuf::from("/root"));
|
||||||
|
home.join(".numa").join("original-resolv.conf")
|
||||||
|
};
|
||||||
|
if let Some(ns) = read_upstream_from_file(backup.to_str().unwrap_or("")) {
|
||||||
|
info!("detected original upstream from backup: {}", ns);
|
||||||
|
return Some(ns);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
fn read_upstream_from_file(path: &str) -> Option<String> {
|
||||||
|
let text = std::fs::read_to_string(path).ok()?;
|
||||||
|
for line in text.lines() {
|
||||||
|
let line = line.trim();
|
||||||
|
if line.starts_with("nameserver") {
|
||||||
|
if let Some(ns) = line.split_whitespace().nth(1) {
|
||||||
|
if ns != "127.0.0.1" && ns != "0.0.0.0" && ns != "::1" {
|
||||||
|
return Some(ns.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Find the upstream for a domain by checking forwarding rules.
|
/// Find the upstream for a domain by checking forwarding rules.
|
||||||
/// Returns None if no rule matches (use default upstream).
|
/// Returns None if no rule matches (use default upstream).
|
||||||
/// Zero-allocation on the hot path — dot_suffix is pre-computed.
|
/// Zero-allocation on the hot path — dot_suffix is pre-computed.
|
||||||
@@ -395,19 +454,28 @@ fn install_service_macos() -> Result<(), String> {
|
|||||||
.status()
|
.status()
|
||||||
.map_err(|e| format!("failed to run launchctl: {}", e))?;
|
.map_err(|e| format!("failed to run launchctl: {}", e))?;
|
||||||
|
|
||||||
if status.success() {
|
if !status.success() {
|
||||||
|
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 uninstall.\n");
|
eprintln!(" Run 'sudo numa service stop' to fully uninstall.\n");
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
|
||||||
Err("launchctl load failed".to_string())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
fn uninstall_service_macos() -> Result<(), String> {
|
fn uninstall_service_macos() -> Result<(), String> {
|
||||||
|
// Restore DNS first, while numa is still running to handle any final queries
|
||||||
|
if let Err(e) = uninstall_macos() {
|
||||||
|
eprintln!(" warning: failed to restore system DNS: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
// Remove plist first so service won't restart on boot even if unload fails
|
// Remove plist first so service won't restart on boot even if unload fails
|
||||||
if let Err(e) = std::fs::remove_file(PLIST_DEST) {
|
if let Err(e) = std::fs::remove_file(PLIST_DEST) {
|
||||||
if e.kind() != std::io::ErrorKind::NotFound {
|
if e.kind() != std::io::ErrorKind::NotFound {
|
||||||
@@ -576,14 +644,24 @@ fn install_service_linux() -> Result<(), String> {
|
|||||||
run_systemctl(&["start", "numa"])?;
|
run_systemctl(&["start", "numa"])?;
|
||||||
|
|
||||||
eprintln!(" Service installed and started.");
|
eprintln!(" Service installed and started.");
|
||||||
|
|
||||||
|
// Set system DNS now that the service is running
|
||||||
|
if let Err(e) = install_linux() {
|
||||||
|
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: journalctl -u numa -f");
|
eprintln!(" Logs: journalctl -u numa -f");
|
||||||
eprintln!(" Run 'sudo numa service stop' to uninstall.\n");
|
eprintln!(" Run 'sudo numa service stop' to fully uninstall.\n");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn uninstall_service_linux() -> Result<(), String> {
|
fn uninstall_service_linux() -> Result<(), String> {
|
||||||
|
// Restore DNS first, while numa is still running
|
||||||
|
if let Err(e) = uninstall_linux() {
|
||||||
|
eprintln!(" warning: failed to restore system DNS: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(e) = run_systemctl(&["stop", "numa"]) {
|
if let Err(e) = run_systemctl(&["stop", "numa"]) {
|
||||||
eprintln!(" warning: {}", e);
|
eprintln!(" warning: {}", e);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user