"""dbanner: the banner shown at SSH login. Without arguments it opens the editor.""" import json import os import subprocess import sys import time from . import banner, widgets from .cli import die, need_root, resolve, run_tui from .i18n import t PROG = "dbanner" SERVICE = "/etc/sv/das-banner" SERVICE_LINK = "/var/service/das-banner" def text(design, use_cache, color): values = widgets.Values(design.get("vars")) blocks = widgets.blocks(design, values, use_cache=use_cache) return banner.render(design, values, blocks, color=color) def wants_login_banner(design): if os.path.exists(banner.OFF_FILE) or os.path.exists(os.path.expanduser("~/.hushlogin")): return False if not sys.stdout.isatty(): return False return bool(os.environ.get("SSH_TTY")) or bool(design.get("local")) def has_cached(design): return any(w.get("enabled") is not False and widgets.mode_of(w) == "cached" for w in design.get("widgets", [])) def set_service(enabled): """Enables the das-banner service (runit) that keeps cached widgets fresh.""" if not os.path.isdir(os.path.dirname(SERVICE_LINK)) or not os.path.isdir(SERVICE): return False if enabled and not os.path.lexists(SERVICE_LINK): os.symlink(SERVICE, SERVICE_LINK) elif not enabled and os.path.lexists(SERVICE_LINK): os.remove(SERVICE_LINK) return True def apply(design): design = banner.normalize(design) banner.save(design, banner.SYSTEM_FILE) os.chmod(banner.SYSTEM_FILE, 0o644) cached = has_cached(design) service = set_service(cached) if service and cached: # The service fills the cache in a moment; this makes the first login see it too subprocess.Popen([sys.executable, os.path.abspath(sys.argv[0]), "refresh"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True) return cached and service USAGE = t("""Використання: dbanner [команда] (без команди) редактор банера з готовими варіантами list готові банери, ваші чернетки й малюнки show [НАЗВА|ФАЙЛ] показати банер (типово — системний) зі свіжими даними --no-color без кольорів apply НАЗВА|ФАЙЛ зробити банером при вході по SSH (/etc/das/banner.json) off | on вимкнути або ввімкнути банер refresh [--force] оновити віджети з кешем (робить служба das-banner) Банер показується при вході по SSH; "local": true у файлі — і при вході в консоль. Окремий користувач вимикає його файлом ~/.hushlogin.""", """Usage: dbanner [command] (no command) banner editor with ready-made designs list ready-made banners, your drafts and pictures show [NAME|FILE] show a banner (default: the system one) with fresh data --no-color without colors apply NAME|FILE make it the SSH login banner (/etc/das/banner.json) off | on turn the banner off or on refresh [--force] update the cached widgets (the das-banner service does it) The banner is shown at SSH logins; "local": true in the file shows it on the console too. A user turns it off for themselves with ~/.hushlogin.""") def main(argv=None): argv = sys.argv[1:] if argv is None else argv if not argv: from . import banner_app return run_tui(PROG, banner_app.BannerApp) cmd, args = argv[0], argv[1:] flags = {a for a in args if a.startswith("--")} args = [a for a in args if not a.startswith("--")] try: if cmd in ("-h", "--help", "help"): print(USAGE) elif cmd == "list": print(t("Готові:", "Ready-made:")) for name, title in banner.list_presets(): print(f" {name:<14} {title}") drafts = banner.list_drafts() if drafts: print(t("Чернетки:", "Drafts:")) for name, path in drafts: print(f" {name:<14} {path}") print(t("Малюнки (вставляються в редакторі):", "Pictures (inserted in the editor):")) for name, title, _ in banner.list_art(): print(f" {name:<14} {title}") elif cmd == "show": if "--login" in flags: design = banner.system_design() if wants_login_banner(design): sys.stdout.write(text(design, use_cache=True, color=True)) sys.stdout.flush() # A hung widget thread must not hold the login os._exit(0) design = banner.load(args[0]) if args else banner.system_design() color = "--no-color" not in flags and sys.stdout.isatty() or "--color" in flags sys.stdout.write(text(design, use_cache=not args, color=color)) elif cmd == "apply": if not args: die(PROG, "вкажіть назву або файл", "name a design or a file") spec = resolve(args[0]) design = banner.load(spec) need_root(["apply", spec]) cached = apply(design) print(t("Банер записано в /etc/das/banner.json, його видно при наступному вході по SSH.", "The banner is written to /etc/das/banner.json; it shows at the next SSH login.")) if cached: print(t("Віджети з кешем оновлює служба das-banner (увімкнено).", "Cached widgets are updated by the das-banner service (enabled).")) elif cmd in ("off", "on"): need_root([cmd]) if cmd == "off": open(banner.OFF_FILE, "w").close() elif os.path.exists(banner.OFF_FILE): os.remove(banner.OFF_FILE) print(t("Банер вимкнено." if cmd == "off" else "Банер увімкнено.", "The banner is off." if cmd == "off" else "The banner is on.")) elif cmd == "refresh": if "--loop" in flags: while True: widgets.refresh(banner.system_design()) time.sleep(60) widgets.refresh(banner.system_design(), force="--force" in flags) else: die(PROG, f"невідома команда: {cmd}\n{USAGE}", f"unknown command: {cmd}\n{USAGE}", 2) except FileNotFoundError as e: die(PROG, f"не знайдено: {e.filename or e}", f"not found: {e.filename or e}") except (ValueError, json.JSONDecodeError) as e: die(PROG, f"помилка у файлі: {e}", f"bad file: {e}") except PermissionError as e: die(PROG, f"немає доступу: {e.filename}", f"permission denied: {e.filename}") return 0 if __name__ == "__main__": sys.exit(main())