Files
Das-OS/srcpkgs/das-shell/files/python/das_shell/cli_banner.py
T
dasandClaude Opus 5.5 a2bffc7fe9
build / build (push) Successful in 38m26s
Terminal: Das prompt, dprompt and dbanner (0.9.1)
- das-shell 0.1.0: the Das two-line bash prompt by default (replaces the
  PS1 of Void's skel .bashrc once), dprompt (Textual prompt builder with
  presets and live preview) and dbanner (SSH login banner editor: canvas
  tools, fields with values, widgets, das-banner service for cached ones,
  Das OS logo and tryzub pictures). Python tests in make test.
- das-server 0.3.0 depends on das-shell; the server installer mentions
  dprompt and dbanner; the desktop image includes it.
- run-qemu.sh forwards the guest's SSH to localhost:2222.
- Hardware fixes: Ventoy menu icon, boot menu titles "Das OS <version>".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 09:17:10 +03:00

155 lines
7.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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())