OperServ: add SET READONLY lockdown
All checks were successful
CI / check (push) Successful in 3m31s

This commit is contained in:
Jean Chevronnet 2026-07-16 01:05:48 +00:00
parent b2442f85e6
commit 0027decdb7
No known key found for this signature in database
8 changed files with 112 additions and 2 deletions

View file

@ -680,11 +680,15 @@ pub struct EventLog {
versions: HashMap<String, u64>, // per-origin highest seq applied (version vector)
entries: Vec<LogEntry>, // full log, kept so peers can pull what they lack
outbound: Option<broadcast::Sender<LogEntry>>, // push newly committed entries to peers
// Operator lockdown (OperServ SET READONLY): while set, locally-authored
// writes are refused. Ephemeral — it resets on restart and never persists,
// and gossip ingestion is unaffected so a read-only node stays in sync.
readonly: bool,
}
impl EventLog {
fn open(path: PathBuf, origin: String) -> (Self, Vec<Event>) {
let mut log = Self { path, origin, lamport: 0, versions: HashMap::new(), entries: Vec::new(), outbound: None };
let mut log = Self { path, origin, lamport: 0, versions: HashMap::new(), entries: Vec::new(), outbound: None, readonly: false };
if let Ok(data) = std::fs::read_to_string(&log.path) {
for line in data.lines().filter(|l| !l.trim().is_empty()) {
match serde_json::from_str::<LogEntry>(line) {
@ -719,6 +723,10 @@ impl EventLog {
// Lamport clock and are pushed to peers; local (channel) events are written
// for restart but never gossiped and carry no version-vector identity.
fn append(&mut self, event: Event) -> std::io::Result<()> {
// Operator lockdown refuses every locally-authored write at this one seam.
if self.readonly {
return Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "services are read-only"));
}
let global = event.scope() == Scope::Global;
let entry = if global {
self.lamport += 1;

View file

@ -197,6 +197,17 @@ impl Db {
self.defcon = level.clamp(1, 5);
}
/// Whether the services are in operator read-only lockdown.
pub fn readonly(&self) -> bool {
self.log.readonly
}
/// Enter or leave read-only lockdown: while on, locally-authored writes are
/// refused. Ephemeral (never persisted); gossip ingestion is unaffected.
pub fn set_readonly(&mut self, on: bool) {
self.log.readonly = on;
}
/// Whether new nick/account registrations are frozen (defcon 3 or lower).
pub fn registrations_frozen(&self) -> bool {
self.defcon <= 3

View file

@ -228,6 +228,12 @@ impl Store for Db {
fn set_defcon(&mut self, level: u8) {
Db::set_defcon(self, level)
}
fn readonly(&self) -> bool {
Db::readonly(self)
}
fn set_readonly(&mut self, on: bool) {
Db::set_readonly(self, on)
}
fn registrations_frozen(&self) -> bool {
Db::registrations_frozen(self)
}