272 lines
12 KiB
Bash
Executable File
272 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
# sysdeck-diagnose.sh — print exactly what Cockpit sees on this system.
|
|
#
|
|
# Run this AFTER `sudo make install` and `sudo systemctl restart cockpit.socket`.
|
|
# Paste the output back if SysDeck still doesn't appear in the Cockpit sidebar.
|
|
#
|
|
# Usage:
|
|
# chmod +x sysdeck-diagnose.sh
|
|
# ./sysdeck-diagnose.sh
|
|
# Or:
|
|
# bash sysdeck-diagnose.sh
|
|
|
|
set -u
|
|
echo "============================================================"
|
|
echo "SysDeck install diagnostic — $(date -Iseconds)"
|
|
echo "============================================================"
|
|
|
|
echo
|
|
echo "─── 1. Cockpit service status ───"
|
|
if command -v systemctl >/dev/null 2>&1; then
|
|
systemctl status cockpit.socket --no-pager 2>&1 | head -8
|
|
echo
|
|
echo "─── 2. Cockpit version (must be >= 239) ───"
|
|
if command -v cockpit-bridge >/dev/null 2>&1; then
|
|
cockpit-bridge --version 2>&1
|
|
else
|
|
echo "cockpit-bridge not in PATH — Cockpit may not be installed correctly"
|
|
echo "On Arch: sudo pacman -S cockpit"
|
|
echo "On Debian: sudo apt install cockpit"
|
|
echo "On Fedora: sudo dnf install cockpit"
|
|
fi
|
|
else
|
|
echo "systemctl not found — not a systemd system?"
|
|
fi
|
|
|
|
echo
|
|
echo "─── 3. What's installed under /usr/share/cockpit/ ───"
|
|
if [ -d /usr/share/cockpit ]; then
|
|
ls -la /usr/share/cockpit/ 2>&1
|
|
else
|
|
echo "/usr/share/cockpit/ does not exist — Cockpit not installed, or install location differs"
|
|
fi
|
|
|
|
echo
|
|
echo "─── 3a. Leftover from old single-plugin layout (v0.0.9-v0.0.19) ───"
|
|
if [ -d /usr/share/cockpit/sysdeck ] && [ ! -L /usr/share/cockpit/sysdeck ]; then
|
|
echo "WARN: /usr/share/cockpit/sysdeck/ exists (old single-plugin layout)"
|
|
echo " This directory was installed by sysdeck v0.0.9 through v0.0.19."
|
|
echo " Cockpit will discover its manifest.json alongside the new"
|
|
echo " sysdeck-* plugins, and may serve old broken code instead of"
|
|
echo " the freshly-installed new code. Remove it:"
|
|
echo " sudo rm -rf /usr/share/cockpit/sysdeck"
|
|
echo " Or run: sudo make uninstall (now removes this in v0.0.24+)"
|
|
ls -la /usr/share/cockpit/sysdeck/ 2>&1 | head -10
|
|
else
|
|
echo "OK: no /usr/share/cockpit/sysdeck/ directory (good — no stale install)"
|
|
fi
|
|
|
|
echo
|
|
echo "─── 4. SysDeck plugins (v0.0.20 architecture: 18 separate plugins) ───"
|
|
SYSDECK_PLUGINS=$(ls -d /usr/share/cockpit/sysdeck-* 2>/dev/null | grep -v sysdeck-common)
|
|
if [ -n "$SYSDECK_PLUGINS" ]; then
|
|
echo "Found $(echo "$SYSDECK_PLUGINS" | wc -l) SysDeck plugins (expected 18):"
|
|
echo "$SYSDECK_PLUGINS" | sed 's|^| |'
|
|
else
|
|
echo "FAIL: no /usr/share/cockpit/sysdeck-* plugin directories found"
|
|
echo " Run: sudo make install"
|
|
fi
|
|
|
|
echo
|
|
echo "─── 4a. Spot-check installed bridge.js for v0.0.23+ fix ───"
|
|
if [ -f /usr/share/cockpit/sysdeck-common/bridge.js ]; then
|
|
if grep -q "const cockpit = window.cockpit" /usr/share/cockpit/sysdeck-common/bridge.js; then
|
|
echo "OK: bridge.js uses window.cockpit (v0.0.23+ fix present)"
|
|
elif grep -q "^import cockpit from" /usr/share/cockpit/sysdeck-common/bridge.js; then
|
|
echo "FAIL: bridge.js still uses 'import cockpit from' (v0.0.22 broken pattern)"
|
|
echo " The page will stay stuck on 'Loading…' because cockpit.js is"
|
|
echo " not an ES module. Reinstall:"
|
|
echo " sudo make uninstall && sudo make install"
|
|
else
|
|
echo "WARN: bridge.js content unknown — paste the first 5 lines:"
|
|
head -5 /usr/share/cockpit/sysdeck-common/bridge.js
|
|
fi
|
|
else
|
|
echo "FAIL: /usr/share/cockpit/sysdeck-common/bridge.js not found"
|
|
fi
|
|
|
|
echo
|
|
echo "─── 5. SysDeck shared bridge + CSS ───"
|
|
if [ -d /usr/share/cockpit/sysdeck-common ]; then
|
|
ls -la /usr/share/cockpit/sysdeck-common/
|
|
else
|
|
echo "FAIL: /usr/share/cockpit/sysdeck-common/ does not exist"
|
|
echo " Each plugin imports ../sysdeck-common/bridge.js — without it,"
|
|
echo " every plugin page will fail to load."
|
|
fi
|
|
|
|
echo
|
|
echo "─── 5a. Python bridge helpers at /usr/lib/sysdeck/bridge/ ───"
|
|
BRIDGE_DIR=/usr/lib/sysdeck/bridge
|
|
if [ -d "$BRIDGE_DIR" ]; then
|
|
PY_COUNT=$(find "$BRIDGE_DIR" -maxdepth 1 -name '*.py' -type f 2>/dev/null | wc -l)
|
|
EXEC_COUNT=$(find "$BRIDGE_DIR" -maxdepth 1 -name '*.py' -type f -perm -a+x 2>/dev/null | wc -l)
|
|
echo "Found $PY_COUNT Python helpers in $BRIDGE_DIR (expected ~16; the 18 plugins"
|
|
echo " invoke them by absolute path: python3 /usr/lib/sysdeck/bridge/<module>.py <subcommand>)."
|
|
if [ "$EXEC_COUNT" -lt "$PY_COUNT" ]; then
|
|
echo "FAIL: $((PY_COUNT - EXEC_COUNT)) of $PY_COUNT helpers are NOT executable."
|
|
echo " bridge.js spawns them via cockpit.spawn(['python3', '<path>.py', ...])."
|
|
echo " cockpit.spawn runs the helper as the calling user — if the file is not"
|
|
echo " marked executable, the spawn may still work (python3 reads it as a script),"
|
|
echo " but file-system-level ACLs or polkit setups can reject it."
|
|
echo " Fix: sudo chmod +x $BRIDGE_DIR/*.py"
|
|
else
|
|
echo "OK: all $EXEC_COUNT helpers are executable."
|
|
fi
|
|
# Smoke-test one helper end-to-end (summary subcommand exists on most modules).
|
|
if [ -x "$BRIDGE_DIR/glances.py" ] || [ -r "$BRIDGE_DIR/glances.py" ]; then
|
|
echo "── 5a-spot: invoke glances.py --help (should print docstring) ──"
|
|
if python3 "$BRIDGE_DIR/glances.py" --help 2>&1 | head -5; then :; else
|
|
echo "FAIL: glances.py failed to run. Python or import error."
|
|
fi
|
|
fi
|
|
# Cross-check the v0.0.28 firmware.devices subcommand bug (the most common silent failure).
|
|
if [ -r "$BRIDGE_DIR/firmware.py" ]; then
|
|
if python3 "$BRIDGE_DIR/firmware.py" devices 2>/dev/null | python3 -c 'import json,sys; d=json.load(sys.stdin); assert isinstance(d.get("Devices"), list)' 2>/dev/null; then
|
|
echo "OK: firmware.py devices subcommand returns {Devices: [...]}"
|
|
else
|
|
echo "FAIL: firmware.py devices subcommand did not return {Devices: [...]}."
|
|
echo " bridge.firmware.devices() in bridge.js calls this — the firmware"
|
|
echo " plugin page will crash. Reinstall: sudo make uninstall && sudo make install."
|
|
fi
|
|
fi
|
|
# Cross-check the v0.0.28 benchmark.run-test subcommand bug.
|
|
if [ -r "$BRIDGE_DIR/benchmark.py" ]; then
|
|
if python3 "$BRIDGE_DIR/benchmark.py" run-test 2>/dev/null | python3 -c 'import json,sys; d=json.load(sys.stdin); assert "raw" in d and "events_per_sec" in d' 2>/dev/null; then
|
|
echo "OK: benchmark.py run-test subcommand returns parsed sysbench result"
|
|
else
|
|
echo "FAIL: benchmark.py run-test subcommand did not return a sysbench result dict."
|
|
echo " bridge.benchmark.runTest(name) in bridge.js calls this — clicking the"
|
|
echo " 'Run' button in the Available Tests table will error out. Reinstall."
|
|
fi
|
|
fi
|
|
else
|
|
echo "FAIL: $BRIDGE_DIR does not exist."
|
|
echo " Every plugin's bridge.js calls python3 $BRIDGE_DIR/<module>.py — without"
|
|
echo " these helpers, every bridge call returns 'No such file or directory'."
|
|
echo " Reinstall: sudo make uninstall && sudo make install"
|
|
fi
|
|
|
|
echo
|
|
echo "─── 6. Spot-check one plugin: sysdeck-containers ───"
|
|
if [ -d /usr/share/cockpit/sysdeck-containers ]; then
|
|
ls -la /usr/share/cockpit/sysdeck-containers/
|
|
echo
|
|
if [ -f /usr/share/cockpit/sysdeck-containers/manifest.json ]; then
|
|
echo "── manifest.json content ──"
|
|
cat /usr/share/cockpit/sysdeck-containers/manifest.json
|
|
echo
|
|
echo
|
|
echo "── manifest.json file metadata ──"
|
|
stat /usr/share/cockpit/sysdeck-containers/manifest.json
|
|
else
|
|
echo "FAIL: /usr/share/cockpit/sysdeck-containers/ exists but manifest.json is missing"
|
|
fi
|
|
else
|
|
echo "FAIL: /usr/share/cockpit/sysdeck-containers/ does not exist"
|
|
fi
|
|
|
|
echo
|
|
echo "─── 7. Can the cockpit user read the plugin manifests? ───"
|
|
if id cockpit >/dev/null 2>&1; then
|
|
OK_COUNT=0
|
|
FAIL_COUNT=0
|
|
for plugin in /usr/share/cockpit/sysdeck-*/; do
|
|
[ -d "$plugin" ] || continue
|
|
if sudo -u cockpit cat "$plugin/manifest.json" >/dev/null 2>&1; then
|
|
OK_COUNT=$((OK_COUNT + 1))
|
|
else
|
|
FAIL_COUNT=$((FAIL_COUNT + 1))
|
|
echo " FAIL: cockpit user cannot read $plugin/manifest.json"
|
|
fi
|
|
done
|
|
echo " OK: $OK_COUNT manifests readable by cockpit user"
|
|
[ "$FAIL_COUNT" -gt 0 ] && echo " FAIL: $FAIL_COUNT manifests NOT readable"
|
|
[ "$FAIL_COUNT" -gt 0 ] && echo " Fix: sudo chmod -R a+rX /usr/share/cockpit/sysdeck-*"
|
|
else
|
|
echo "NOTE: 'cockpit' user does not exist on this system (Cockpit may use a different service user)"
|
|
echo " On most distros Cockpit runs as the 'cockpit' user; on some it runs as 'root'."
|
|
fi
|
|
|
|
echo
|
|
echo "─── 8. AppStream metainfo ───"
|
|
METAINFO_PATH=/usr/share/metainfo/sysdeck.metainfo.xml
|
|
if [ -f "$METAINFO_PATH" ]; then
|
|
ls -la "$METAINFO_PATH"
|
|
echo
|
|
if command -v appstreamcli >/dev/null 2>&1; then
|
|
echo "─── 9. AppStream validation of metainfo ───"
|
|
appstreamcli validate "$METAINFO_PATH" 2>&1 || true
|
|
echo
|
|
echo "─── 10. AppStream cache search for sysdeck ───"
|
|
appstreamcli search sysdeck 2>&1 | head -20 || true
|
|
else
|
|
echo "appstreamcli not installed — install appstream to validate metainfo"
|
|
echo "On Arch: sudo pacman -S appstream"
|
|
fi
|
|
else
|
|
echo "NOTE: $METAINFO_PATH does not exist"
|
|
echo " Without metainfo, SysDeck appears in the sidebar but NOT in the Cockpit"
|
|
echo " Applications install menu. Run: sudo make install (installs metainfo too)"
|
|
fi
|
|
|
|
echo
|
|
echo "─── 11. Cockpit journal — recent sysdeck/error/warning lines ───"
|
|
if command -v journalctl >/dev/null 2>&1; then
|
|
journalctl -u cockpit -n 200 --no-pager 2>&1 | grep -iE 'sysdeck|error|warning|fail|cannot|denied' | tail -30
|
|
if [ ${PIPESTATUS[0]} -ne 0 ]; then
|
|
echo "(no cockpit unit found — Cockpit may not be running as a systemd service)"
|
|
fi
|
|
else
|
|
echo "journalctl not available"
|
|
fi
|
|
|
|
echo
|
|
echo "─── 12. Cockpit config (if any) ───"
|
|
if [ -f /etc/cockpit/cockpit.conf ]; then
|
|
cat /etc/cockpit/cockpit.conf
|
|
else
|
|
echo "(no /etc/cockpit/cockpit.conf — using defaults)"
|
|
fi
|
|
|
|
echo
|
|
echo "─── 13. Reference: working plugin manifests shipped in this tarball ───"
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
for ref in cockpit-incus cockpit-machines cockpit-ostree; do
|
|
refpath="$SCRIPT_DIR/standalone-plugins/$ref/manifest.json"
|
|
if [ -f "$refpath" ]; then
|
|
echo
|
|
echo "── $ref/manifest.json ──"
|
|
cat "$refpath"
|
|
fi
|
|
done
|
|
|
|
echo
|
|
echo "─── 14. Compare: is sysdeck's manifest structurally similar? ───"
|
|
echo "(compare the keys in sysdeck's manifest above to the reference plugins above)"
|
|
echo
|
|
echo "If sysdeck's manifest has keys that the reference plugins don't have (like"
|
|
echo "'content', 'title', 'priority', or 'path' inside menu entries), Cockpit"
|
|
echo "may silently reject it. The v0.0.19 manifest was rewritten to match"
|
|
echo "the cockpit-podman reference pattern exactly. If the installed manifest"
|
|
echo "doesn't match, you may have an old version installed — run:"
|
|
echo
|
|
echo " sudo rm -rf /usr/share/cockpit/sysdeck"
|
|
echo " sudo make install"
|
|
echo " sudo systemctl restart cockpit.socket"
|
|
|
|
echo
|
|
echo "─── 15. Final smoke test: load Cockpit and check the sidebar ───"
|
|
echo "Open https://localhost:9090 in a browser. Log in. Look at the left sidebar."
|
|
echo "SysDeck should appear as an entry (typically under 'System')."
|
|
echo
|
|
echo "If it's NOT there, also check:"
|
|
echo " - Open browser devtools (F12) → Network tab → reload → look for /cockpit/@localhost/sysdeck/ requests"
|
|
echo " - Hard-refresh with Ctrl+Shift+R (cockpit-ws caches manifests aggressively)"
|
|
echo " - Check the journal again AFTER opening the page (Cockpit may log the discovery attempt)"
|
|
|
|
echo
|
|
echo "============================================================"
|
|
echo "End of diagnostic."
|
|
echo "============================================================"
|