# SysDeck — Quick Start Author: **Jeremy Anderson** · · Version: **0.2.0** (Master Edition) Five-minute path from tarball to 26 sidebar entries in your Cockpit — plus the Web Edition with Fester pre-integrated (section 9). --- ## 1. Prerequisites | Component | Why | Install | |-----------|-----|---------| | `cockpit-bridge` ≥ 239 | The plugin runtime | `pacman -S cockpit` / `apt install cockpit` / `dnf install cockpit` | | `python3` ≥ 3.9 | Bridge helpers | Universal on modern Linux | | `polkit` | Privilege escalation (the cockpit way) | `pacman -S polkit` / `apt install policykit-1` / `dnf install polkit` | | `appstream` (optional) | Cockpit Applications menu | `pacman -S appstream` / `apt install appstream` | Cockpit itself ships its own `cockpit-bridge` package — that is the only hard dependency. SysDeck degrades gracefully when optional backends (podman, nftables, mkosi, bpftool, apparmor, …) are absent — each panel renders an install hint instead of crashing. ## 2. Install ```bash # Get the tarball ls sysdeck-0.0.35.tar.bz2 # download from your release source # Extract and install tar xjf sysdeck-0.0.35.tar.bz2 cd sysdeck-0.0.35 sudo make install # Restart cockpit so it re-scans the plugin directory sudo systemctl restart cockpit.socket ``` `make install` does: - Copies each `plugins/sysdeck-*/{manifest.json,index.html,*.js}` to `/usr/share/cockpit/sysdeck-*/` - Copies `shared/{manifest.json,bridge.js,sysdeck.css}` to `/usr/share/cockpit/sysdeck-common/` - Copies each `bridge/*.py` (executable, 0755) to `/usr/lib/sysdeck/bridge/` - Copies `firewall/templates/*.sh` (executable, 0755) to `/usr/share/sysdeck/firewall/templates/` - Installs the AppStream metainfo at `/usr/share/metainfo/sysdeck.metainfo.xml` - Installs the polkit policy at `/usr/share/polkit-1/actions/org.sysdeck.policy` - Reloads polkit and refreshes the AppStream cache ## 3. Verify the install Open `https://:9090` in your browser and authenticate as a wheel/sudo user. The Cockpit sidebar should now list **23** entries under the `SysDeck ` prefix: | # | Module | # | Module | |---|--------|---|--------| | 1 | SysDeck Containers | 13 | SysDeck Themes | | 2 | SysDeck Firewall | 14 | SysDeck Hardware Auth | | 3 | SysDeck Integrity | 15 | SysDeck Glances | | 4 | SysDeck Network Security | 16 | SysDeck Sensors | | 5 | SysDeck Service Mesh | 17 | SysDeck Benchmark | | 6 | SysDeck Vault | 18 | SysDeck Packages | | 7 | SysDeck Fleet | 19 | SysDeck Policy | | 8 | SysDeck Kata | 20 | SysDeck Databases | | 9 | SysDeck Fester | 21 | SysDeck Jellyfin | | 10 | SysDeck Firmware | 22 | SysDeck Photos | | 11 | SysDeck Image Builder | 23 | SysDeck Remote FS | | 12 | SysDeck Mining | | | If any are missing, run the diagnostic: ```bash sudo /usr/share/sysdeck/sysdeck-diagnose.sh ``` It prints exactly what cockpit sees on your system — installed manifests, bridge helpers present, polkit actions loaded, and the cockpit-bridge version. ## 4. First-use walkthrough ### 4a. Firewall (the cockpit way) Open **SysDeck Firewall**. The panel renders: 1. **Capability matrix** — confirms nftables is installed. 2. **Template selector** — pick `vps-webserver` (service-aware firewall that auto-detects SSH/Caddy/Varnish/Forgejo) or `no-services` (locked-down host with no public services except SSH). 3. **Detect Services** — runs the template's `detect` action and shows the OS, interface, IPv4/IPv6, and which services the template found. 4. **Apply Template** — cockpit prompts for the superuser password via polkit. The bridge runs the template's `start` action under the `org.sysdeck.firewall.modify` action. 5. **Banned IPs** — live `ssh_abuse` / `port_scanners` / `connlimit_abuse` ban sets, with per-IP **Unban** buttons and a **Clear All** button. 6. **Active Ruleset** — the live nftables rules table, refreshed after each operation. To drop in your own template, copy a `*.sh` file into `/usr/share/sysdeck/firewall/templates/` — the panel's `templates` subcommand discovers it automatically. The script must implement `start / stop / restart / detect / status` subcommands (see the shipped templates for reference). ### 4b. Packages (the cockpit way) Open **SysDeck Packages**. Click **⬆ Update All**. Cockpit prompts for the superuser password via polkit. The bridge runs `pacman -Syu` / `apt upgrade -y` / `dnf upgrade -y` directly via subprocess — no `sudo` shell-out from JS. Live stdout/stderr stream into the in-panel `
` log. The **👁 Preview Command** button shows the exact command that will be run before you confirm.

### 4c. Policy & Permissions

Open **SysDeck Policy**. The panel renders:

1. **LSM Stack** — a badge row in the header showing which LSMs the kernel has stacked (`/sys/kernel/security/lsm`), and a table of all 9 supported LSMs with their securityfs paths and active/inactive status.
2. **Capability Matrix** — confirms availability of ACLs, cgroups v2, VLANs, eBPF, namespaces, file caps, and each LSM. Absent concerns show a red "no" badge plus the install command.
3. **ACL Manager** — pick a path, type an entry like `group:www-data:rwx`, click `setfacl -m` (or `setfacl -x` to remove, `set default ACL` for directory inheritance).
4. **cgroups v2** — the unified hierarchy tree under `/sys/fs/cgroup/` with per-cgroup process counts and controllers. Use **Show** to inspect one cgroup's processes and control files; **mkdir** to create a new one; **move** to migrate a PID; **write** to set `memory.max`, `cpu.weight`, etc.
5. **VLANs** — list and create/delete 802.1Q VLANs via `ip link add ... type vlan id `.
6. **eBPF programs** — list loaded BPF programs via `bpftool prog show -j`, list maps, pin a program to `/sys/fs/bpf/...`.
7. **Namespaces** — `lsns -J` output as a table.
8. **File Capabilities** — `getcap -r /` enumeration with `setcap` / `getcap` / `setcap -r` controls.
9. **AppArmor** (optional) — if the kernel compiled AppArmor in, shows the enforcement mode and lets you switch profiles between `enforce` and `complain` modes. If absent, renders an install hint.
10. **Smack / TOMOYO / Yama / LoadPin / Lockdown / BPF-LSM / Landlock** — each has its own card with the live state and any management controls the LSM supports. Each card follows the same shape: if the LSM is not active, the card shows the kernel cmdline that enables it; if active, it shows the live state.

### 4d. Databases

Open **SysDeck Databases**. The panel auto-detects 32+ engines across SQL (PostgreSQL/MySQL/MariaDB/SQLite/CockroachDB/TiDB), NoSQL (MongoDB/CouchDB/RethinkDB/DynamoDB-local), Vector (Milvus/Qdrant/Weaviate/Chroma/pgvector), TimeSeries (InfluxDB/TimescaleDB/QuestDB/ClickHouse), Graph (Neo4j/ArangoDB/OrientDB), Embedded (Redis/KeyDB/ValKey/RocksDB/LMDB/BadgerDB), Cloud (Firestore-emulator/Supabase-local), and AI (LanceDB/DuckDB/Tile38). Each row has **▶ Start / ■ Stop / ↻ Restart / 🔍 Status** buttons. The **Run SQL Query** card lets you execute arbitrary SQL against SQL-family engines via the engine's CLI client (`psql -tAc`, `mysql -e`, etc.).

## 5. Build from source

```bash
cd sysdeck-0.0.35
make check         # 7 build-time guards: manifests, metainfo, tabs, no-broken-import, no-broken-module, bridge-subcommands cross-check, version sync
make dist          # builds sysdeck-0.0.35.tar.bz2
make distcheck     # extracts + runs make check inside the tarball tree
```

`make check` is a hard pre-flight: it cross-checks every `bridgeCmd("", ["", ...])` call in `shared/bridge.js` against the `COMMANDS` dict declared in each `bridge/.py`. If the JS calls a subcommand the Python helper doesn't implement, `make check` fails with a clear message naming the file, line, and missing subcommand.

## 6. Uninstall

```bash
sudo make uninstall
sudo systemctl restart cockpit.socket
```

`make uninstall` removes every trace of every prior version (the v0.0.9-v0.0.19 single-plugin `/usr/share/cockpit/sysdeck/` directory, the v0.0.20+ multi-plugin `/usr/share/cockpit/sysdeck-*/` directories, the Python bridge helpers, the diagnostic scripts, the firewall templates, the AppStream metainfo, the polkit policy, and any pacman-installed `sysdeck` package).

## 7. Where to go next

- [README.md](./README.md) — full module catalog, architecture, coding standards.
- [BLOG.md](./BLOG.md) — release narrative for v0.0.33 and prior versions.
- [docs/INSTALL.md](./docs/INSTALL.md) — RPM, DEB, pip, and manual install paths.
- [QA.md](./QA.md) — QA notes per release.
- [worklog.md](./worklog.md) — per-task development log.

## 8. Reporting issues

Open the in-panel error view: every SysDeck plugin's `index.html` installs `window.addEventListener('error')` and `'unhandledrejection'` handlers that replace the "Loading…" placeholder with the actual error message on the page — no devtools required. The same page tells you whether `cockpit.js` itself loaded, whether `bridge.js` imported cleanly, and whether the panel's `mount()` threw.

## 9. The Web Edition (master tarball)

The master tarball also ships the **SysDeck Web Edition** at `web/` — a standalone browser console (no cockpit required) with 29 bridge modules, real `/proc` / `/sys` collectors, and **Fester pre-integrated** (vendored at `web/mini-services/fester`, independent version 0.2.1):

```bash
make web-dev        # fester service in the background (:3010) + web console (:3000)
```

Manual equivalent:

```bash
make fester-start                              # terminal 1: fester on :3010
cd web && bun install && bun run db:push       # terminal 2: web edition setup
bun run dev                                    #            web console on :3000
```

Open `http://localhost:3000`. The master tarball can be rebuilt any time with `make master`.

## 10. The AI Gateway (master tarball, v0.3.0)

The master tarball also vendors **klanker-gate** — the Frosty Deno LLM gateway (independent version 0.9.0, Apache-2.0, **by TykoDev: https://github.com/TykoDev/klanker-gate — not SysDeck code**, see `klanker-gate/ATTRIBUTION.md`) — at `klanker-gate/`, with the new **AI Gateway** module in both editions. On Arch Linux the whole gateway is one package away:

```bash
cd klanker-gate/arch
pacman -S --needed deno base-devel     # deno is in [extra]
makepkg -si                            # /usr/share/klanker-gate + systemd unit
sudoedit /etc/klanker-gate/env         # FROSTY_PG_URL + one provider key (+ token)
sudo systemctl enable --now klanker-gate
curl http://localhost:8080/healthz
```

Then point SysDeck at it (cockpit bridge env, or `web/.env` for the web edition, then restart):

```bash
KLANKER_URL=http://127.0.0.1:8080
KLANKER_ADMIN_TOKEN=
```

Both the cockpit AI Gateway panel and the web edition's AI Gateway panel flip from their offline/demo state to live data automatically. The full runbook — postgres provisioning, multi-worker serving (`FROSTY_WORKERS`, an Arch bonus via `SO_REUSEPORT`), the optional control-UI build — is `klanker-gate/arch/INSTALL-ARCH.md`.

### 10.1 Running an all-local stack (ollama · llama.cpp · koboldcpp)

The gateway is **not SaaS-only** — no API key is required anywhere in this
setup. Five provider types are local-first upstream: `ollama`, `lmstudio`,
`sgl` (SGLang) natively, plus the generic `openai-compatible` type that
llama.cpp (llama-server), KoboldCpp, vLLM and TGI all speak:

| backend | provider type | base URL | auth |
|---|---|---|---|
| Ollama | `ollama` | `http://127.0.0.1:11434/v1` | none |
| llama.cpp (llama-server) | `openai-compatible` | `http://127.0.0.1:8081/v1` | optional |
| KoboldCpp | `openai-compatible` | `http://127.0.0.1:5001/v1` | optional |
| LM Studio | `lmstudio` | `http://127.0.0.1:1234/v1` | none |
| SGLang | `sgl` | `http://127.0.0.1:30000/v1` | none |

Env wiring (in `/etc/klanker-gate/env` or the gateway's `.env`):

```bash
OLLAMA_BASE_URL=http://127.0.0.1:11434/v1
OLLAMA_MODELS=qwen3:14b,llama3.1:8b,nomic-embed-text
LMSTUDIO_BASE_URL=http://127.0.0.1:1234/v1
OPENAI_COMPAT_BASE_URL=http://127.0.0.1:8081/v1   # ONE openai-wire server
```

Env registers one `openai-compatible` account — to run llama.cpp **and**
koboldcpp (and vLLM) side by side, register each via the admin API, then
auto-discover its catalog:

```bash
curl -s http://127.0.0.1:8080/api/providers -H 'Authorization: Bearer $FROSTY_ADMIN_TOKEN' \
  -H 'content-type: application/json' \
  -d '{"id":"llama-server","type":"openai-compatible","baseUrl":"http://127.0.0.1:8081/v1","enabled":true}'
curl -s -X POST http://127.0.0.1:8080/api/providers/llama-server/refresh-models \
  -H 'Authorization: Bearer $FROSTY_ADMIN_TOKEN'
```

**Port note:** llama-server defaults to `:8080` — the same port the gateway
listens on. Run it on another port (`--port 8081`) or move the gateway.

Both editions ship a **Local stack wiring** card (in the AI Gateway panel)
that live-probes each backend's `/v1/models` from the host and shows these
recipes with copy buttons — `klanker localstack` at the bridge level.

### 10.2 Turning the AI Gateway off (module toggles)

Not using the gateway (or switched to a different assistant stack)?
Both editions let you remove it from the console without uninstalling
anything:

- **web edition** — every sidebar module carries a power toggle (hover
  a row → ⏻). Clicking it hides the module from the sidebar AND the
  ⌘K palette; a **Disabled (N)** section appears at the sidebar bottom
  with one-click re-enable (plus a restore-all ↻). State is persisted
  in SQLite (`shell.disabled` via the `shell` bridge module) and
  survives restarts; Overview is protected. If you disable the module
  you are viewing, the console jumps back to Overview.
- **cockpit edition** — plugins are discovered by directory: `sudo rm
  -rf /usr/share/cockpit/sysdeck-klanker` removes the sidebar entry
  (bridge helper stays at `/usr/lib/sysdeck/bridge/klanker.py` for
  scripts); restore with `sudo make install`.

### 10.3 The 0.3.0 security audit (both editions + the vendored gateway)

A full-codebase security review shipped with 0.3.0 — the cockpit bridge
helpers, the 27 plugin panels, the web edition, and the vendored
klanker-gate tree. What changed:

- **bridge helpers fail closed now.** `cgroup-set` validates both the
  cgroup path (must resolve under `/sys/fs/cgroup`) and the control-file
  name (real controller knobs only); `artifacts-clear` /
  `build-delete` / `build-log` / `artifacts` validate ids as single
  path components before touching state/artifacts/logs dirs;
  `profile-create` rejects names that aren't single components (was
  directory traversal + config injection into root-executed build
  configs); hwalert's `sudo sh -c` is gone (direct write, device path
  validated under the scanned sysfs bases); `db start/stop/restart`
  resolve engines through the registry; `db query` now actually
  enforces the read-only promise (SELECT/WITH/SHOW/… only);
  `themes set` rejects newlines (cockpit.conf section injection);
  `packages install/remove/update` reject option-shaped names.
- **every plugin escapes its data.** The 8 oldest panels (packages,
  benchmark, auth, sensors, vault, firmware, mesh, and the auth quick
  actions) now escape every interpolated string — package metadata,
  USB reader descriptors, fwupd device fields, sensor labels, spawn
  errors — before it lands in `innerHTML`. All 27 manifests dropped
  `unsafe-eval` from their CSP. Every external link carries
  `rel="noopener noreferrer"`.
- **the web edition binds loopback.** `bun run dev` → `127.0.0.1:3000`,
  the fester service → `127.0.0.1:3010`, the production start script
  pins `HOSTNAME=127.0.0.1`; the bridge endpoint gained a body-size
  cap, a per-IP rate limit and generic error responses (details go to
  the server log).
- **the vendored gateway got audited, not modified.** Findings live in
  `klanker-gate/arch/SECURITY-UPSTREAM.md` (10 findings, 3 critical:
  no-token admin mode, 0.0.0.0 default bind, open `/v1/*` until the
  first virtual key exists). Upstream source stays byte-identical per
  the attribution contract; the SysDeck `arch/` packaging layer
  mitigates: the systemd unit refuses to start without
  `FROSTY_ADMIN_TOKEN`, `INSTALL-ARCH.md` §9 carries the firewall +
  first-vkey runbook.
- **fixed along the way (functional):** the Packages panel's
  firewall-backend install path (`packages.py install --` choke), the
  auth panel's quick-action buttons (called a bridge.spawn that never
  existed), and the mesh panel's table (read a data shape the bridge
  never returned).

### 10.4 The web edition login (Unix accounts, cockpit-style)

SysDeck is a **LAN-side console** — loopback binding stays the outer
boundary. What 0.4.0 changes is the login itself: instead of the 0.3.1
shared password, you now sign in with a **Unix account — the username
and password are verified by the host's PAM stack**, exactly the
mechanism Cockpit uses at its own login screen. The host decides; the
console keeps no password data of its own.

- **PAM path:** `web/scripts/pam-auth.py` (stdlib-only ctypes client of
  `libpam`) runs the `pam_start` → `pam_authenticate` → `pam_acct_mgmt`
  sequence under the **`sysdeck`** service when `/etc/pam.d/sysdeck`
  exists, else the stock **`login`** stack. Credentials travel over
  stdin (never argv — `/proc` would leak them). Ship your own
  `/etc/pam.d/sysdeck` (e.g. `auth required pam_unix.so`, plus
  `pam_google_authenticator` for MFA if you want it) to tailor the
  stack — `SYSDECK_PAM_SERVICE` renames it.
- **Root, or pam+local:** pam_unix needs root to read `/etc/shadow`
  for *arbitrary* users (non-root processes only get the invoking uid
  via `unix_chkpwd` — a pam_unix guarantee). So the modes are
  `SYSDECK_AUTH_MODE=pam` (default; run the service as root, like
  cockpit-ws), `pam+local` (PAM first, then the `SdUser` scrypt table
  for installs that can't run privileged), or `local` (console
  accounts only). Manage the local table with
  `bun scripts/manage-users.mjs list|add|passwd|disable|enable|remove`
  from `web/`.
- **Session:** an HttpOnly, SameSite=Lax cookie (`sd_session`) holding
  an HMAC-SHA256-signed token **bound to the username**
  (`v2...`), **12h** expiry. The HMAC key is random
  per install and persists in the SQLite DB, so sessions survive
  restarts — including the 0.3.1 → 0.4.0 upgrade (old v1 tokens still
  verify as a legacy "operator" session until they age out).
- **Gate scope:** the page itself is server-rendered as the login
  screen until the cookie verifies, every `/api/*` route answers 401
  until signed in, and the **fester service verifies the identical
  v2 token** on its REST + WebSocket surface — no unauthenticated path
  into the console's data.
- **Lockout:** wrong attempts are rate limited per-IP **and**
  per-username (5 per 60s each — the same shape the sshd stack
  applies). Wrong-user and wrong-password return the same generic
  answer; nothing enumerates accounts.
- **Identity in the shell:** the header carries an account menu —
  avatar, `user@host`, unix-account provenance (PAM vs local), the
  wheel/sudo "Administrative access" badge, and a live session-expiry
  countdown with a draining life bar; the status bar shows
  `user@host` next to the vitals. Login/logout are audited with the
  unix username as the actor.
- **TLS:** LAN deployments typically run plain http; front the console
  with TLS and set `SYSDECK_SESSION_SECURE=1` to add the `Secure`
  cookie flag. Sign out lives in the account menu (clears the cookie).

The login/logout actions are audited (`module: web`, actions
`login` / `login-failed` / `logout`, actor = the unix username, with
source IP). This is deliberately *not* MFA-by-default or rate-proof
crypto — it is the host's own account system doing what it already
does at every other login surface on the box, recorded here so nobody
mistakes it for more or less than that.

### 10.5 Cockpit module detection in the web console (v0.4.1)

The console scans the host the same way the cockpit shell discovers
pages — every `/usr/share/cockpit//manifest.json` with a `menu`
entry is a module — and **loads each one into its own navigation**:

- a **Cockpit** sidebar group (with a LIVE/DEMO provenance badge) lists
  every detected module — distro modules (`cockpit-machines`,
  `cockpit-podman`, networking, storage, accounts, updates, SELinux,
  PCP metrics, kdump, tuned...) and third-party addons alike;
- each module opens a detail view with its manifest identity, shipped
  files, **live backend presence probes** (`virsh`/`podman`/`nmcli`/
  `pkcon`/... — real `which()` checks), and a jump to the native
  console panel covering the domain when one exists;
- `sysdeck-*` modules never duplicate (native panels already ship), and
  menu-less chrome (`base1`, `shell`) is skipped — exactly the cockpit
  shell's own rules;
- `SYSDECK_COCKPIT_SCAN` (colon-separated paths) adds extra scan roots
  for staged trees; with no cockpit tree on the host, a clearly-badged
  typical-distro set keeps the surface explorable;
- the **Cockpit Modules** hub panel (Integrations group) summarizes
  detection: counts, backend availability, native coverage, and the
  scan paths in play.

This is the piece that makes the console/host pair 100% compatible:
install a cockpit module on the box, and it shows up here — no cockpit
login required to browse it.

## 11. Run without Cockpit (the complete standalone runbook, v0.3.0)

The web edition needs **nothing from sections 1–8** — no cockpit, no Python
bridge, no systemd, no root. One Bun runtime serves the whole console:

```bash
tar xjf sysdeck-0.4.1-master.tar.bz2
cd sysdeck-0.4.1-master
make web-dev          # bun install + db:push + fester + next dev :3000
```

Production path (standalone build, systemd on Arch, reverse proxy with the
`?XTransformPort=` websocket gateway, environment reference, troubleshooting):

```bash
cd web
bun run build                              # self-contained .next/standalone/
PORT=3000 HOSTNAME=0.0.0.0 bun run start   # or: node .next/standalone/server.js
```

The complete runbook — with the two systemd units (web + fester), the
`.env` reference table, the Caddy/nginx websocket-gateway configs and a
troubleshooting matrix — lives in two places, kept in sync:

- **`web/README.md`** in this tarball (plain markdown)
- the **"Run without Cockpit" panel** in the web console (system group,
  right under Overview) — every command block has a copy button

## 12. The web-edition skin for Cockpit (v0.3.0)

Since 0.3.0 the Cockpit plugin pages wear the **web-edition skin** by
default: every plugin's `index.html` links
`../sysdeck-common/sysdeck-web.css` after the base stylesheet, porting
the Next.js console's midnight/teal design (teal accent `#3fc9b0`,
soft-tinted badges, 10px radii, tabular numerals, thin teal-edged
scrollbars) onto the classic cockpit panels. Nothing else changes — the
class vocabulary, the bridge, and every module are untouched.

```bash
# revert the plugin pages to the classic 0.1.x skin:
sudo rm /usr/share/cockpit/sysdeck-common/sysdeck-web.css

# also theme the Cockpit SHELL chrome (sidebar, header, login) to match:
sudo make install-branding      # backs up any existing branding.css first
sudo make uninstall-branding    # restore the backup
```

`install-branding` installs `shared/branding.css` as
`/usr/share/cockpit/branding.css` — Cockpit's documented override point
for the shell. It targets both PatternFly v5 (`pf-v5-*`, Cockpit ≥ 300)
and v4 (`pf-c-*`) selector generations, so unmatched rules simply no-op.

Author: **Jeremy Anderson** ·  ·