From dc85ff17e58e401e27d33d0a61d73bb737e34f4e Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Sun, 7 Feb 2021 19:02:24 +0100 Subject: [PATCH] bot: watch journald and send events --- Cargo.lock | 119 ++++++++++++++++++++++++++++++++++++++++++++-------- Cargo.toml | 2 + src/bot.rs | 78 +++++++++++++++++++++++++++++++++- src/main.rs | 5 ++- 4 files changed, 184 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9613186..8ce4823 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,9 +130,11 @@ version = "0.1.0" dependencies = [ "anyhow", "clap", + "futures", "matrix-sdk", "serde", "serde_yaml", + "systemd", "thiserror", "tokio", "tracing-subscriber", @@ -166,6 +168,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "build-env" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cf89846ef2b2674ef1c153256cec98fba587c72bf4ea2c4b2f6d91a19f55926" + [[package]] name = "bumpalo" version = "3.6.0" @@ -190,6 +198,12 @@ version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" @@ -322,7 +336,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -331,7 +345,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "const_fn", "crossbeam-utils", "lazy_static", @@ -346,7 +360,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" dependencies = [ "autocfg", - "cfg-if", + "cfg-if 1.0.0", "lazy_static", ] @@ -360,6 +374,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "cstr-argument" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20bd4e8067c20c7c3a4dea759ef91d4b18418ddb5bd8837ef6e2f2f93ca7ccbb" +dependencies = [ + "cfg-if 0.1.10", + "memchr", +] + [[package]] name = "ctr" version = "0.6.0" @@ -375,7 +399,7 @@ version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "num_cpus", ] @@ -406,7 +430,7 @@ version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "801bbab217d7f79c0062f4f7205b5d4427c6d1a7bd7aafdd1475f7c59d62b283" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -421,7 +445,28 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-shared", + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.0", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63f713f8b2aa9e24fec85b0e290c56caee12e3b6ae0aeeda238a75b28251afd6" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -430,6 +475,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "foreign-types-shared" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7684cf33bb7f28497939e8c7cf17e3e4e3b8d9a0080ffa4f8ae2f515442ee855" + [[package]] name = "form_urlencoded" version = "1.0.0" @@ -589,7 +640,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "js-sys", "libc", "wasi", @@ -770,7 +821,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "js-sys", "time 0.2.25", "wasm-bindgen", @@ -819,6 +870,17 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cca32fa0182e8c0989459524dc356b8f2b5c10f1b9eb521b7d182c03cf8c5ff" +[[package]] +name = "libsystemd-sys" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e03fd580bcecda68dcdcd5297085ade6a3dc552cd8b030d2b94a9b089ef7ab8" +dependencies = [ + "build-env", + "libc", + "pkg-config", +] + [[package]] name = "linked-hash-map" version = "0.5.4" @@ -840,7 +902,7 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -1089,8 +1151,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "038d43985d1ddca7a9900630d8cd031b56e4794eecc2e9ea39dd17aa04399a70" dependencies = [ "bitflags", - "cfg-if", - "foreign-types", + "cfg-if 1.0.0", + "foreign-types 0.3.2", "lazy_static", "libc", "openssl-sys", @@ -1138,7 +1200,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ccb628cad4f84851442432c60ad8e1f607e29752d0bf072cbd0baf28aa34272" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "instant", "libc", "redox_syscall 0.1.57", @@ -1748,7 +1810,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa827a14b29ab7f44778d14a88d3cb76e949c45083f7dbfa507d0cb699dc12de" dependencies = [ "block-buffer", - "cfg-if", + "cfg-if 1.0.0", "cpuid-bool 0.1.2", "digest", "opaque-debug", @@ -1806,7 +1868,7 @@ version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "winapi", ] @@ -1904,13 +1966,28 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "systemd" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f722cabda922e471742300045f56dbaa53fafbb4520fca304e51258019bfe91d" +dependencies = [ + "cstr-argument", + "foreign-types 0.5.0", + "libc", + "libsystemd-sys", + "log", + "memchr", + "utf8-cstr", +] + [[package]] name = "tempfile" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "rand", "redox_syscall 0.2.4", @@ -2104,7 +2181,7 @@ version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2254,6 +2331,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf8-cstr" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55bcbb425141152b10d5693095950b51c3745d019363fc2929ffd8f61449b628" + [[package]] name = "uuid" version = "0.8.2" @@ -2304,7 +2387,7 @@ version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55c0f7123de74f0dab9b7d00fd614e7b19349cd1e2f5252bbe9b1754b59433be" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "serde", "serde_json", "wasm-bindgen-macro", @@ -2331,7 +2414,7 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3de431a2910c86679c34283a33f66f4e4abd7e0aec27b6669060148872aadf94" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "js-sys", "wasm-bindgen", "web-sys", diff --git a/Cargo.toml b/Cargo.toml index 3da5694..7fe7e51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,11 +9,13 @@ edition = "2018" [dependencies] anyhow = "1.0" clap = "3.0.0-beta.2" +futures = "0.3" tokio = { version = "1", features = [ "full" ] } tracing-subscriber = "0.2" url = { version = "2.2", features = [ "serde" ] } serde_yaml = "0.8" serde = "1.0" +systemd = "0.8" thiserror = "1.0" [dependencies.matrix-sdk] diff --git a/src/bot.rs b/src/bot.rs index 97f3312..b2a6387 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -3,11 +3,19 @@ use std::{ io::{BufReader, BufWriter}, }; -use matrix_sdk::{Client, ClientConfig, Session, SyncSettings}; +use matrix_sdk::{ + events::{ + room::message::{MessageEventContent, TextMessageEventContent}, + AnyMessageEventContent, + }, + Client, ClientConfig, Session, SyncSettings, +}; +use systemd::{journal, JournalRecord}; use crate::autojoin::AutoJoinHandler; use crate::Config; +#[derive(Clone)] pub struct BadNewsBot { client: Client, config: Config, @@ -46,8 +54,76 @@ impl BadNewsBot { /// [`BadNewsBot::init`] **must** be called before this function, otherwise the [`Client`] isn't /// logged in. pub async fn run(&self) { + let clone = self.clone(); + + tokio::task::spawn_blocking(move || clone.watch_journald()); + self.client.sync(SyncSettings::default()).await } + + fn watch_journald(&self) { + let mut reader = journal::OpenOptions::default() + .system(true) + .open() + .expect("Could not open journal"); + + // Seek to end of current log to prevent old messages from being printed + reader + .seek_tail() + .expect("Could not seek to end of journal"); + + // HACK: for some reason calling `seek_tail` above still leaves old entries when calling + // next, so skip all those before we start the real logging + loop { + if reader.next().unwrap() == 0 { + break; + } + } + + // NOTE: Ugly double loop, but low level `wait` has to be used if we don't want to miss any + // new entry. See https://github.com/jmesmon/rust-systemd/issues/66 + loop { + loop { + let record = reader.next_entry().unwrap(); + match record { + Some(record) => self.handle_record(record), + None => break, + } + } + + reader.wait(None).unwrap(); + } + } + + fn handle_record(&self, record: JournalRecord) { + const KEY_UNIT: &str = "_SYSTEMD_UNIT"; + const KEY_MESSAGE: &str = "MESSAGE"; + + if let Some(unit) = record.get(KEY_UNIT) { + if !self.config.units.contains(unit) { + return; + } + + let message = record.get(KEY_MESSAGE); + let message = format!( + "[{}] {}", + unit.strip_suffix(".service").unwrap_or(unit), + message.map(|m| m.as_ref()).unwrap_or("") + ); + let content = AnyMessageEventContent::RoomMessage(MessageEventContent::Text( + TextMessageEventContent::plain(message), + )); + let room_id = self.config.room_id.clone(); + let client_clone = self.client.clone(); + + tokio::spawn(async move { + client_clone + .room_send(&room_id, content, None) + .await + .unwrap(); + }); + } + } } /// This loads the session information from an existing file, and tries to login with it. If no such diff --git a/src/main.rs b/src/main.rs index 1b26d28..dee3102 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use std::{ + collections::HashSet, fs::File, io::{self, BufReader}, path::PathBuf, @@ -32,7 +33,7 @@ struct Opts { } /// Holds the configuration for the bot. -#[derive(Deserialize)] +#[derive(Clone, Deserialize)] pub struct Config { /// The URL for the homeserver we should connect to homeserver: Url, @@ -45,6 +46,8 @@ pub struct Config { /// ID of the Matrix room where the bot should post messages. The bot will only accept /// invitations to this room. room_id: RoomId, + /// Units to watch for logs + units: HashSet, } #[tokio::main]