#!/usr/bin/env python3 """ generate-plugins.py — emit 18 standalone Cockpit plugins + shared bridge. This script regenerates the plugins/ and shared/ directories from the _old_modules/ source files (the v0.0.19 src/modules/*.js). Each module becomes a self-contained Cockpit plugin at plugins/sysdeck-/. Run from the project root: python3 scripts/generate-plugins.py """ import json import shutil from pathlib import Path ROOT = Path(__file__).resolve().parent.parent OLD_MODULES = ROOT / "_old_modules" PLUGINS_DIR = ROOT / "plugins" SHARED_DIR = ROOT / "shared" # The 22 visible modules we ship as standalone Cockpit plugins # (v0.0.35: SysDeck Kata restored as a standalone sidebar entry, # plus three new modules — Jellyfin, Photos, Remote FS). # Each entry: (plugin_dir_name, label, order, has_bridge_helper, module_file_stem) MODULES = [ ("sysdeck-containers", "Containers & VMs", 20, True, "containers"), ("sysdeck-firewall", "Firewall", 21, True, "firewall"), ("sysdeck-integrity", "Integrity", 22, True, "integrity"), ("sysdeck-netsec", "Network Security", 23, True, "netsec"), ("sysdeck-mesh", "Service Mesh", 24, True, "mesh"), ("sysdeck-vault", "Vault", 25, True, "vault"), ("sysdeck-fleet", "Fleet", 26, True, "fleet"), # v0.0.35: SysDeck Kata restored to its own sidebar entry per # user directive: "kata containers should be called SysDeck Kata # and moved out of the tools area." The plugin hosts the pre-built # cockpit-kata React app directly — no longer embedded as a tab # inside sysdeck-containers. The standalone cockpit-kata sub-project # remains consolidated into SysDeck. ("sysdeck-kata", "Kata", 27, False, "kata"), ("sysdeck-fester", "Fester", 28, True, "fester"), ("sysdeck-firmware", "Firmware", 29, True, "firmware"), ("sysdeck-builder", "Image Builder", 30, True, "builder"), ("sysdeck-mining", "Mining", 31, True, "mining"), ("sysdeck-themes", "Themes", 32, True, "themes"), ("sysdeck-auth", "Hardware Auth", 33, True, "auth"), ("sysdeck-glances", "Glances", 34, True, "glances"), ("sysdeck-sensors", "Sensors", 35, True, "sensors"), ("sysdeck-benchmark", "Benchmark", 36, True, "benchmark"), ("sysdeck-packages", "Packages", 37, True, "packages"), # v0.0.32: two new modules — Policy & Permissions (new) + DB Control (restored). ("sysdeck-policy", "Policy", 38, True, "policy"), ("sysdeck-db", "Databases", 39, True, "db"), # v0.0.35: three new modules — Jellyfin, Photos, Remote FS. # Per user directive: "next we will integrate a jellyfin # management module where it starts, stops, and loads the # admin panel in the module as well as a photo manager of # equal quality. with its own module. then a remote fs manager # such as ceph, and others but not nfs or amanada fs" ("sysdeck-jellyfin", "Jellyfin", 40, True, "jellyfin"), ("sysdeck-photos", "Photos", 41, True, "photos"), ("sysdeck-remotefs", "Remote FS", 42, True, "remotefs"), # v0.0.39: shared tabbed Prometheus + Grafana monitoring module. ("sysdeck-monitoring", "Monitoring", 43, True, "monitoring"), ] KEYWORDS = { "containers": ["containers", "podman", "docker", "oci", "images", "pods", "vm", "isolation"], "firewall": ["firewall", "nftables", "iptables", "rules", "filter"], "integrity": ["integrity", "lynis", "audit", "hardening", "trust"], "netsec": ["network", "sockets", "ports", "listening", "ss", "tcp", "udp"], "mesh": ["mesh", "kubernetes", "kubectl", "services", "k8s"], "vault": ["vault", "luks", "cryptsetup", "encryption", "volumes"], "fleet": ["fleet", "uptime", "load", "hosts", "machines"], # v0.0.35: Kata restored to its own keywords entry. "kata": ["kata", "sandbox", "vm", "isolation", "kata-runtime", "kata-containers", "kata-monitor", "microvm", "hardware-virtualization", "qcrows", "pxe", "tftp", "cloud-hypervisor", "firecracker", "qemu"], "fester": ["fester", "build", "orchestration", "dag", "compose"], "firmware": ["firmware", "fwupd", "tpm", "bios", "update"], "builder": ["builder", "mkosi", "vmdb2", "archiso", "live-build", "image", "compose", "blueprint"], "mining": ["mining", "xmrig", "monero", "hashrate", "worker"], "themes": ["themes", "appearance", "cockpit.conf", "styling"], "auth": ["auth", "smartcard", "pkcs11", "opensc", "kerberos", "ssh"], "glances": ["glances", "monitoring", "cpu", "memory", "disk", "network"], "sensors": ["sensors", "temperature", "fan", "voltage", "lm_sensors"], "benchmark": ["benchmark", "sysbench", "stress", "performance"], "packages": ["packages", "pacman", "dnf", "apt", "updates", "install"], "policy": ["policy", "acl", "getfacl", "setfacl", "cgroups", "cgroup", "vlan", "ebpf", "bpf", "namespace", "lsns", "apparmor", "permissions", "groups", "mac", "smack", "tomoyo", "yama", "loadpin", "lockdown", "landlock", "lsm", "setcap", "getcap", "capabilities", "ptrace"], "db": ["database", "db", "sql", "postgresql", "mysql", "mariadb", "sqlite", "mongodb", "redis", "valkey", "influxdb", "neo4j", "clickhouse", "milvus", "qdrant", "weaviate", "duckdb"], # v0.0.35 new keyword sets: "jellyfin": ["jellyfin", "media", "server", "movies", "tv", "music", "streaming", "transcoding", "subsonic", "emby"], "photos": ["photos", "photo", "gallery", "photoprism", "piwigo", "lychee", "nextcloud", "memories", "librephotos", "image", "album", "media"], "remotefs": ["remotefs", "remote", "filesystem", "distributed", "ceph", "cephfs", "gluster", "glusterfs", "moosefs", "beegfs", "orangefs", "cluster", "storage", "shared"], # v0.0.39: shared tabbed Prometheus + Grafana module. "monitoring": ["prometheus", "grafana", "monitoring", "metrics", "dashboards", "alerts", "alertmanager", "pushgateway", "time series", "observability", "targets", "scrape", "datasources", "panels", "visualization"], } def write_manifest(plugin_name, label, order, keywords): m = { "version": 0, "name": plugin_name, # NOTE: bare version number, NOT ">=239". Cockpit's packages.py # uses sortify_version() which 0-pads each numeric component — # ">=239" would be parsed as ">=00000239" which is GREATER than any # real cockpit version (because '>' is ASCII 62 > '0' ASCII 48), # causing the manifest to be silently rejected at install time. # Pattern verified from pkg/systemd/manifest.json ("cockpit": "265"). "requires": {"cockpit": "239"}, "menu": { "index": { "label": label, "order": order, "keywords": [{"matches": keywords}], } }, "content-security-policy": "default-src 'self' 'unsafe-inline' 'unsafe-eval'", } return json.dumps(m, indent=4) + "\n" def write_index_html(plugin_name, module_stem, label): return f""" {label}
Loading…
""" BRIDGE_JS = '''\ /* * SysDeck — shared bridge.js * * Provides the `bridge` and `EventBus` objects that each SysDeck Cockpit * plugin imports. Wraps cockpit.spawn() calls to the Python bridge helpers * under /usr/lib/sysdeck/bridge/ (invoked as `python3 -m sysdeck.bridge.`). * * COCKPIT.JS LOADING CONTRACT (verified from cockpit source code): * - pkg/base1/cockpit.js is a UMD/IIFE script that sets `window.cockpit` * as a global. It is NOT an ES module — it has no `export` statements. * - Plugins load it via `` in * their index.html (see pkg/systemd/index.html for the reference pattern). * - At build time, cockpit's esbuild plugin (build.js:71-83) rewrites * `import cockpit from "cockpit"` in plugin source to * `module.exports = cockpit` — i.e., a reference to the global. * - SysDeck plugins don't go through esbuild, so we access the global * `window.cockpit` directly. The index.html already loads cockpit.js * via