From aabc95a538d17177ac324c2fa9324913568b9574 Mon Sep 17 00:00:00 2001 From: Ahmed Date: Tue, 16 Jun 2026 23:02:43 +0300 Subject: fix: few alfred commands about services and mlmmj --- alfred/helpstrings.py | 3 +- alfred/mlmmj.py | 122 ++++++++++++++++++++++++-------------------------- alfred/server.py | 67 +++++++++++++++++++++------ 3 files changed, 115 insertions(+), 77 deletions(-) (limited to 'alfred') diff --git a/alfred/helpstrings.py b/alfred/helpstrings.py index 90c7dbb..e81676b 100644 --- a/alfred/helpstrings.py +++ b/alfred/helpstrings.py @@ -1,6 +1,6 @@ TOPICS = "topics: srv, bot, files, irc, coffee, vpn, list, git. type !help for details" -SRV_TOPIC = "srv: uptime disk mem load status net top topmem io conns who logs. type !help srv for details" +SRV_TOPIC = "srv: uptime disk mem load status net top topmem io conns who logs scan. type !help srv for details" SRV = { "uptime": "srv uptime: system uptime and load averages. usage: !srv uptime", "disk": "srv disk: disk usage for root. usage: !srv disk", @@ -14,6 +14,7 @@ SRV = { "conns": "srv conns: TCP connection summary. usage: !srv conns", "who": "srv who: currently logged-in users. usage: !srv who", "logs": "srv logs: last n lines from a service log. usage: !srv logs service or !srv logs service n", + "scan": "srv scan: run clamscan on a path. usage: !srv scan or !srv scan path (allowed: mail, 0x0 files, tmp, quarantine)", } BOT_TOPIC = "bot: list, all, name start stop restart status. type !help bot for details" diff --git a/alfred/mlmmj.py b/alfred/mlmmj.py index b691a7c..bb5c162 100644 --- a/alfred/mlmmj.py +++ b/alfred/mlmmj.py @@ -1,22 +1,18 @@ """ Sopel plugin: mlmmj mailing list management - !list subscribe - subscribe (forced, no confirmation email) - !list unsubscribe - unsubscribe - !list members - show all subscribers - !list send - send a message to the list as owner (bypasses moderation) - !list mod - show pending moderation queue - !list approve - approve a held post - !list discard - discard a held post + !list subscribe - subscribe to list@gumx.cc + !list unsubscribe - unsubscribe + !list members - show all subscribers + !list send - send a message to the list as owner """ -import email.utils, glob, mailbox, os, subprocess, tempfile +import email.utils, glob, os, subprocess, tempfile from sopel import plugin LIST_DIR = "/var/spool/mlmmj/list" LIST_ADDR = "list@gumx.cc" OWNER_ADDR = "ahmed@gumx.cc" -MOD_DIR = os.path.join(LIST_DIR, "moderation") def _owner(bot, trigger): @@ -28,9 +24,9 @@ def cmd_list(bot, trigger): if not _owner(bot, trigger): return - args = (trigger.group(2) or "").split(None, 1) + args = (trigger.group(2) or "").split(None, 2) if not args: - bot.say("usage: !list ") + bot.say("usage: !list ") return action = args[0] @@ -41,7 +37,7 @@ def cmd_list(bot, trigger): return email_addr = args[1].strip() r = subprocess.run( - ["/usr/bin/mlmmj-sub", "-L", LIST_DIR, "-a", email_addr, "-s", "-f"], + ["mlmmj-sub", "-L", LIST_DIR, "-a", email_addr, "-s", "-f"], capture_output=True, text=True ) bot.say(f"subscribed: {email_addr}" if r.returncode == 0 @@ -53,7 +49,7 @@ def cmd_list(bot, trigger): return email_addr = args[1].strip() r = subprocess.run( - ["/usr/bin/mlmmj-unsub", "-L", LIST_DIR, "-a", email_addr, "-s", "-f"], + ["mlmmj-unsub", "-L", LIST_DIR, "-a", email_addr, "-s", "-f"], capture_output=True, text=True ) bot.say(f"unsubscribed: {email_addr}" if r.returncode == 0 @@ -89,7 +85,7 @@ def cmd_list(bot, trigger): tmpname = f.name try: r = subprocess.run( - ["/usr/bin/mlmmj-send", "-L", LIST_DIR, "-l", "1", "-m", tmpname], + ["mlmmj-send", "-L", LIST_DIR, "-m", tmpname], capture_output=True, text=True ) bot.say("sent" if r.returncode == 0 @@ -98,55 +94,55 @@ def cmd_list(bot, trigger): os.unlink(tmpname) elif action == "mod": - files = sorted(os.listdir(MOD_DIR)) if os.path.isdir(MOD_DIR) else [] - if not files: - bot.say("moderation queue empty") - return - for fname in files[:5]: - fpath = os.path.join(MOD_DIR, fname) - try: - with open(fpath, "rb") as f: - msg = mailbox.Message(f) - subj = (msg.get("Subject", "(no subject)") or "")[:50] - frm = (msg.get("From", "") or "")[:30] - except Exception: - subj, frm = "(unreadable)", "" - bot.say(f"{fname} {subj} — {frm}") - if len(files) > 5: - bot.say(f"... and {len(files) - 5} more") - - elif action == "approve": - args2 = (trigger.group(2) or "").split(None, 2) - if len(args2) < 2: - bot.say("usage: !list approve ") - return - fid = args2[1].strip() - path = os.path.join(MOD_DIR, fid) - if not os.path.isfile(path): - bot.say(f"not found: {fid}") - return - r = subprocess.run( - ["/usr/bin/mlmmj-send", "-L", LIST_DIR, "-l", "1", "-m", path], - capture_output=True, text=True - ) - if r.returncode == 0: - os.unlink(path) - bot.say(f"approved: {fid}") - else: - bot.say(f"error: {(r.stderr or r.stdout).strip()}") + sub = args[1].strip() if len(args) > 1 else "" + mod_dir = os.path.join(LIST_DIR, "moderation") + + if not sub or sub == "list": + if not os.path.isdir(mod_dir): + bot.say("mod: no moderation queue") + return + items = sorted(os.listdir(mod_dir)) + if not items: + bot.say("mod: queue empty") + else: + bot.say(f"mod: {len(items)} pending") + for name in items[:10]: + bot.say(f" {name}") + + elif sub == "approve": + if len(args) < 3: + bot.say("usage: !list mod approve ") + return + fname = args[2].strip() + fpath = os.path.join(mod_dir, fname) + if not os.path.exists(fpath): + bot.say(f"mod: not found: {fname}") + return + r = subprocess.run( + ["mlmmj-moderate", "-L", LIST_DIR, "-m", fpath, "-a"], + capture_output=True, text=True + ) + bot.say("approved" if r.returncode == 0 + else f"error: {(r.stderr or r.stdout).strip()}") - elif action == "discard": - args2 = (trigger.group(2) or "").split(None, 2) - if len(args2) < 2: - bot.say("usage: !list discard ") - return - fid = args2[1].strip() - path = os.path.join(MOD_DIR, fid) - if not os.path.isfile(path): - bot.say(f"not found: {fid}") - return - os.unlink(path) - bot.say(f"discarded: {fid}") + elif sub == "reject": + if len(args) < 3: + bot.say("usage: !list mod reject ") + return + fname = args[2].strip() + fpath = os.path.join(mod_dir, fname) + if not os.path.exists(fpath): + bot.say(f"mod: not found: {fname}") + return + r = subprocess.run( + ["mlmmj-moderate", "-L", LIST_DIR, "-m", fpath, "-d"], + capture_output=True, text=True + ) + bot.say("rejected" if r.returncode == 0 + else f"error: {(r.stderr or r.stdout).strip()}") + + else: + bot.say("usage: !list mod [list|approve |reject ]") else: - bot.say("usage: !list ") + bot.say("usage: !list ") diff --git a/alfred/server.py b/alfred/server.py index cfb4c69..a369b88 100644 --- a/alfred/server.py +++ b/alfred/server.py @@ -15,6 +15,7 @@ Commands (owner only) - use !server or !srv: !server conns - TCP connection summary !server who - logged-in users !server logs [n] - last n lines of service log (default 10) + !server scan [path] - run clamscan on path (default: mail) """ import os @@ -28,18 +29,30 @@ if _alfred_dir not in _sys.path: import helpstrings as h SERVICES = [ - "nginx", "postfix", "dovecot", "ngircd", "soju", + "nginx", "postfix", "dovecot", "ngircd", "soju", "websocat", "opendkim", "fcgiwrap", "alfred", "dcron", "0x0", + "xandikos", "wg-quick.wg0", ] LOG_PATHS = { - "nginx": ["/var/log/nginx/error.log", "/var/log/nginx/access.log"], - "postfix": ["/var/log/mail.log", "/var/log/messages"], - "dovecot": ["/var/log/dovecot.log", "/var/log/messages"], - "ngircd": ["/var/log/ngircd.log", "/var/log/messages"], - "soju": ["/var/log/soju.log", "/var/log/messages"], - "alfred": ["/var/log/alfred/sopel.log", "/var/log/alfred/daemon.log"], - "herald": ["/var/log/herald/sopel.log", "/var/log/herald/daemon.log"], + "nginx": ["/var/log/nginx/error.log", "/var/log/nginx/access.log"], + "postfix": ["/var/log/mail.log", "/var/log/messages"], + "dovecot": ["/var/log/dovecot.log", "/var/log/messages"], + "ngircd": ["/var/log/ngircd.log", "/var/log/messages"], + "soju": ["/var/log/soju.log"], + "websocat": ["/var/log/websocat/daemon.log"], + "0x0": ["/var/log/0x0/daemon.log", "/var/log/0x0/prune.log"], + "xandikos": ["/var/log/xandikos/daemon.log"], + "alfred": ["/var/log/alfred/sopel.log", "/var/log/alfred/daemon.log"], + "herald": ["/var/log/herald/sopel.log", "/var/log/herald/daemon.log"], + "bikabot": ["/var/log/bikabot/sopel.log", "/var/log/bikabot/daemon.log"], + "daft": ["/var/log/daft/sopel.log", "/var/log/daft/daemon.log"], + "daffy": ["/var/log/daffy/sopel.log", "/var/log/daffy/daemon.log"], + "contessa": ["/var/log/contessa/sopel.log", "/var/log/contessa/daemon.log"], + "osterman": ["/var/log/osterman/sopel.log", "/var/log/osterman/daemon.log"], + "salvador": ["/var/log/salvador/sopel.log", "/var/log/salvador/daemon.log"], + "shireen": ["/var/log/shireen/sopel.log", "/var/log/shireen/daemon.log"], + "jeeves": ["/var/log/jeeves/sopel.log", "/var/log/jeeves/daemon.log"], } @@ -63,11 +76,13 @@ def _uptime(bot, _args): def _disk(bot, _args): - bot.say(_run(["df", "-h", "/"])) + for line in _run(["df", "-h", "/"]).splitlines(): + bot.say(line) def _mem(bot, _args): - bot.say(_run(["free", "-h"])) + for line in _run(["free", "-h"]).splitlines(): + bot.say(line) def _load(bot, _args): @@ -90,7 +105,7 @@ def _status(bot, args): capture_output=True, text=True, timeout=5 ) out = (r.stdout + r.stderr).strip() - state = "started" if "started" in out else "stopped" if "stopped" in out else out[:20] + state = "UP" if "started" in out else "DOWN" if "stopped" in out else out[:20] results.append(f"{svc}:{state}") bot.say(" | ".join(results)) @@ -201,12 +216,37 @@ def _io(bot, _args): def _conns(bot, _args): - bot.say(_run(["ss", "-s"])) + for line in _run(["ss", "-s"]).splitlines(): + bot.say(line) def _who(bot, _args): out = _run(["who"]) - bot.say(out if out.strip() else "who: no users logged in") + if not out.strip() or out == "(no output)": + bot.say("who: no users logged in") + return + for line in out.splitlines(): + bot.say(line) + + +def _scan(bot, args): + path = args[0] if args else "/home/ahmed/mail" + allowed = ("/home/ahmed/mail", "/var/0x0/files", "/tmp", "/var/quarantine") + if not any(path.startswith(p) for p in allowed): + bot.say(f"scan: path not allowed: {path}") + return + bot.say(f"scan: starting clamscan on {path} ...") + r = subprocess.run( + ["clamscan", "-r", "--quiet", "--infected", path], + capture_output=True, text=True, timeout=300 + ) + out = (r.stdout + r.stderr).strip() + if not out: + bot.say("scan: no infected files found") + else: + for line in out.splitlines()[:15]: + bot.say(line) + bot.say(f"scan: done (exit {r.returncode})") def _logs(bot, args): @@ -244,6 +284,7 @@ _SUBCOMMANDS = { "conns": _conns, "who": _who, "logs": _logs, + "scan": _scan, } -- cgit v1.2.3