rehash: 'echoircd rehash' CLI + SIGHUP reload the running config in place via a pidfile
This commit is contained in:
parent
42fe82556b
commit
a7ae0d86a9
4 changed files with 96 additions and 3 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -22,3 +22,6 @@ bin/
|
|||
# local backups / scratch config copies
|
||||
*.bak-*
|
||||
echoircd.conf.*
|
||||
|
||||
# runtime pidfile
|
||||
*.pid
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ name = "echoircd"
|
|||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
# SIGHUP → config rehash, without unsafe (the crate keeps signal setup internal).
|
||||
signal-hook = "0.3"
|
||||
# TLS backend: openssl. The crate keeps all `unsafe` internal, so the daemon
|
||||
# stays `#![forbid(unsafe_code)]`. A pure-Rust `rustls` backend can slot in beside it.
|
||||
openssl = "0.10"
|
||||
|
|
|
|||
21
src/ircd.rs
21
src/ircd.rs
|
|
@ -101,6 +101,8 @@ pub enum Event {
|
|||
},
|
||||
/// Background timer tick — drives ping/idle timeouts.
|
||||
Tick,
|
||||
/// Re-read the config file and apply it live (from SIGHUP / the `rehash` CLI).
|
||||
Rehash,
|
||||
}
|
||||
|
||||
/// Insert an extra IRCv3 tag into a wire line's tag block, creating the `@…`
|
||||
|
|
@ -323,6 +325,7 @@ impl Ircd {
|
|||
let _ = reply.send(resp);
|
||||
}
|
||||
Event::Tick => self.on_tick(),
|
||||
Event::Rehash => self.on_rehash(),
|
||||
}
|
||||
self.drain_hooks();
|
||||
}
|
||||
|
|
@ -643,6 +646,24 @@ impl Ircd {
|
|||
|
||||
/// Background timer: PING idle clients, reap the unresponsive and the
|
||||
/// never-registered.
|
||||
/// SIGHUP / `rehash` CLI: re-read the config and apply it live, keeping the
|
||||
/// running config if the file can't be read (same policy as /REHASH).
|
||||
fn on_rehash(&mut self) {
|
||||
let path = self.server.conf_path.clone();
|
||||
eprintln!("rehashing server config file.");
|
||||
match Config::try_load(&path) {
|
||||
Some(fresh) => {
|
||||
self.server.announce("The server is rehashing its configuration.");
|
||||
self.server.apply_config(fresh);
|
||||
self.server.announce("Server configuration reloaded.");
|
||||
eprintln!("server configuration is reloaded.");
|
||||
}
|
||||
None => {
|
||||
eprintln!("rehash: could not read {path} — the running configuration was kept.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_tick(&mut self) {
|
||||
self.server.ping_links(); // keepalive on every server link
|
||||
self.server.purge_xlines(); // drop expired server bans
|
||||
|
|
|
|||
73
src/main.rs
73
src/main.rs
|
|
@ -20,10 +20,51 @@ use echoircd::ircd::{Event, Ircd};
|
|||
use echoircd::socketengine;
|
||||
use echoircd::tls::{OpensslBackend, TlsBackend};
|
||||
|
||||
/// `echoircd rehash [config]`: read the running server's pidfile and send it
|
||||
/// SIGHUP so it reloads its config in place, then exit. No unsafe — the signal is
|
||||
/// sent via the `kill` command.
|
||||
fn rehash_cli(cfgpath: &str) -> i32 {
|
||||
let cfg = Config::load(cfgpath);
|
||||
let pidfile = cfg
|
||||
.raw
|
||||
.get("pidfile")
|
||||
.and_then(|v| v.first())
|
||||
.cloned()
|
||||
.unwrap_or_else(|| "echoircd.pid".to_string());
|
||||
let pid = match std::fs::read_to_string(&pidfile) {
|
||||
Ok(s) => s.trim().to_string(),
|
||||
Err(_) => {
|
||||
eprintln!("echoircd: no pidfile at {pidfile} — is the server running?");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
if pid.parse::<u32>().is_err() {
|
||||
eprintln!("echoircd: bad pidfile {pidfile} (contents: {pid:?})");
|
||||
return 1;
|
||||
}
|
||||
println!("rehashing server config file.");
|
||||
let sent = std::process::Command::new("kill")
|
||||
.args(["-s", "HUP", &pid])
|
||||
.status()
|
||||
.map(|st| st.success())
|
||||
.unwrap_or(false);
|
||||
if !sent {
|
||||
eprintln!("echoircd: could not signal pid {pid} — is the server running?");
|
||||
return 1;
|
||||
}
|
||||
println!("server configuration is reloaded.");
|
||||
0
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let path = std::env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "echoircd.conf".to_string());
|
||||
let mut args = std::env::args().skip(1);
|
||||
let first = args.next();
|
||||
// `echoircd rehash [config]` signals a running server instead of booting one.
|
||||
if first.as_deref() == Some("rehash") {
|
||||
let cfgpath = args.next().unwrap_or_else(|| "echoircd.conf".to_string());
|
||||
std::process::exit(rehash_cli(&cfgpath));
|
||||
}
|
||||
let path = first.unwrap_or_else(|| "echoircd.conf".to_string());
|
||||
let cfg = Config::load(&path);
|
||||
|
||||
// precompute the bcrypt constants off-thread so the first hash never stalls the core
|
||||
|
|
@ -61,6 +102,17 @@ fn main() {
|
|||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Write a pidfile (default echoircd.pid) so `echoircd rehash` can find us.
|
||||
let pidfile = cfg
|
||||
.raw
|
||||
.get("pidfile")
|
||||
.and_then(|v| v.first())
|
||||
.cloned()
|
||||
.unwrap_or_else(|| "echoircd.pid".to_string());
|
||||
if let Err(e) = std::fs::write(&pidfile, format!("{}\n", std::process::id())) {
|
||||
eprintln!("echoircd: could not write pidfile {pidfile}: {e}");
|
||||
}
|
||||
|
||||
// global queue limits (per-class overrides layer on top of these in the reactor)
|
||||
let raw_num = |k: &str, d: usize| {
|
||||
cfg.raw
|
||||
|
|
@ -122,6 +174,21 @@ fn main() {
|
|||
}
|
||||
});
|
||||
|
||||
// SIGHUP → live config rehash (the mechanism the `rehash` CLI uses).
|
||||
match signal_hook::iterator::Signals::new([signal_hook::consts::SIGHUP]) {
|
||||
Ok(mut signals) => {
|
||||
let sig_tx = tx.clone();
|
||||
thread::spawn(move || {
|
||||
for _ in signals.forever() {
|
||||
if sig_tx.send(Event::Rehash).is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(e) => eprintln!("echoircd: SIGHUP handler unavailable: {e}"),
|
||||
}
|
||||
|
||||
// reactor worker pool: shared by the plaintext acceptor and the direct-TLS
|
||||
// acceptor, so client I/O (framing + TLS crypto) spreads across cores.
|
||||
let reactors =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue