Codifies the markdown format Sam applied in bd94b87 / 30cf590 as a project rule rather than per-file judgment. Prettier 3 defaults with proseWrap=preserve (no prose reflow), printWidth=80, embedded code formatting off so we don't touch fenced shell/JSON blocks. .prettierignore scopes Prettier to active docs: - excludes tmp/, cache/, node_modules/, build artifacts - excludes CHANGELOG.md + RELEASE-NOTES-*.md (hand-formatted, rigid) - excludes .archive/ and .opencode/ (historical / tooling internal) - excludes bundled bootstrap.html Reformatted 16 active .md files: padded markdown tables, blank line before lists (CommonMark-strict), `*emph*` -> `_emph_`. No content changes — diffs are all whitespace/alignment/emphasis style. Verified: `npx prettier@3 --check '**/*.md'` reports all clean. Build: not run — docs + tooling config only. Tests: pass — prettier --check is green; git diff confirms no content deletions, only formatting. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
22 KiB
22 KiB
Clawdie Admin Panel — System Management UI
Purpose: bsddialog-based system administration interface for Clawdie desktop users
Philosophy: Pure shell + bsddialog (no external GUI toolkits), inspired by PC-BSD warden's CLI approach
Overview
clawdie-admin is a lightweight terminal UI that lets users manage:
- Clawdie service — start/stop, view status
- Jails — list, start/stop, open console (jexec)
- ZFS snapshots — create, list, restore (inspired by Life-Preserver)
- System health — CPU, RAM, disk usage, pool status
- Logs — tail service and jail logs
- Configuration — change assistant name, timezone, LLM provider
Entry Points
1. Desktop Launcher / Panel Entry
Right-click system tray
└─ "Clawdie Admin"
└─ Launches /usr/local/bin/clawdie-admin
2. CLI (Terminal)
$ clawdie-admin
# Opens interactive menu
3. Desktop Launcher
Launch ~/.local/share/applications/clawdie-admin.desktop from the desktop or file manager.
Menu Structure
Main Menu
┌─ Clawdie Admin Panel ──────────────────────────────────────┐
│ │
│ Quick Status │
│ ├─ Clawdie service: ✓ Running (pid 42315) │
│ ├─ ZFS pool (zroot): 15.2 GB / 100 GB (15% used) │
│ ├─ Memory: 2.3 GB / 8.0 GB (29%) │
│ ├─ CPU Load: 0.24 (1 min avg) │
│ ├─ Audio: ✓ OSS (Intel HDA) │
│ └─ Network: ✓ em0 (DHCP, 192.168.1.100) │
│ │
│ [ System Health ] [ Jails ] [ Snapshots ] │
│ [ Hardware ] [ Config ] [ Quit ] │
│ │
└─────────────────────────────────────────────────────────────┘
System Health Submenu
┌─ System Health ────────────────────────────────────────┐
│ │
│ CPU Load │
│ │ 1-min: 0.24 │
│ │ 5-min: 0.18 │
│ │ 15-min: 0.12 │
│ │ │
│ Memory Usage │
│ │ Used: 2.3 GB │
│ │ Free: 5.7 GB │
│ │ Cache: 0.8 GB │
│ │ │
│ ZFS Pool: zroot │
│ │ Used: 15.2 GB │
│ │ Free: 84.8 GB │
│ │ Pool health: ✓ ONLINE │
│ │ │
│ Clawdie Service │
│ │ Status: ✓ Running │
│ │ PID: 42315 │
│ │ Memory: 145 MB │
│ │ │
│ [ Back ] │
│ │
└────────────────────────────────────────────────────────┘
Hardware Submenu
┌─ Hardware Detection ───────────────────────────────────────┐
│ │
│ Audio Card │
│ │ Device: Intel HDA (pci0:27:0:0) │
│ │ Status: ✓ Working (OSS) │
│ │ Mixer: /dev/mixer0 │
│ │ │
│ Network Interfaces │
│ │ em0: Ethernet (DHCP) — 192.168.1.100 │
│ │ re0: Ethernet (not configured) │
│ │ Wireless: None detected │
│ │ Note: WiFi firmware can be installed post-boot │
│ │ │
│ Other Devices │
│ │ USB: 3 devices detected │
│ │ Bluetooth: Not found (optional feature, v1.0+) │
│ │ │
│ [ Configure Audio ] [ Network Setup ] [ Back ] │
│ │
└────────────────────────────────────────────────────────────┘
Post-Install Suggestions:
If WiFi detected but no firmware:
⚠️ WiFi Device Detected: Intel AC 8260
→ Install firmware: sudo pkg install iwm-firmware-8260
→ Then restart: sudo service netif restart
If audio not working:
⚠️ No audio output detected
→ Check: cat /dev/sndstat
→ Install: pkg install snd-hda-generic (for Intel/AMD)
→ Install: pkg install snd-usb-audio (for USB headsets)
Jails Submenu
┌─ Clawdie Jails ────────────────────────────────────────┐
│ │
│ (N) Name Status Process Count IP Address │
│ 1) worker Running 4 10.0.0.101 │
│ 2) db Running ✓ Healthy 10.0.0.102 │
│ 3) cms Running ✓ Ready 10.0.0.103 │
│ 4) mgmt Stopped -- 10.0.0.104 │
│ │
│ Manage jail: │
│ [ Boot All ] [ Stop All ] [ Back ] │
│ │
│ Select jail number (1-4) to manage: │
│ >_ │
│ │
└────────────────────────────────────────────────────────┘
# After selecting jail (e.g., "1" for worker):
┌─ Jail: worker ──────────────────────────────────────────┐
│ │
│ Status: ✓ Running │
│ IPv4: 10.0.0.101 │
│ PID: 42350 │
│ Memory: 234 MB │
│ │
│ [ Start ] [ Stop ] [ Restart ] │
│ [ Console (jexec) ] [ Back ] │
│ │
└─────────────────────────────────────────────────────────┘
Snapshots Submenu
┌─ ZFS Snapshots ────────────────────────────────────────┐
│ │
│ Snapshot Management │
│ [ Create Snapshot ] │
│ [ List Snapshots ] │
│ [ Restore Snapshot ] │
│ │
│ Auto-Backup Status │
│ │ Last backup: 2026-03-23 18:45 UTC │
│ │ Interval: Daily at 02:00 │
│ │ Retention: 7 days │
│ │ Status: ✓ Enabled │
│ │
│ [ Back ] │
│ │
└────────────────────────────────────────────────────────┘
# After selecting "List Snapshots":
┌─ ZFS Snapshots on zroot ──────────────────────────────┐
│ │
│ Snapshot Created Size │
│ zroot@auto-2026-03-23 2026-03-23 02:00 2.3 GB │
│ zroot@auto-2026-03-22 2026-03-22 02:00 2.1 GB │
│ zroot@manual-20260323_1842 2026-03-23 18:42 1.8 GB │
│ zroot@manual-20260323_1200 2026-03-23 12:00 1.7 GB │
│ │
│ Total snapshots: 4 │
│ Total space: 7.9 GB │
│ │
│ Select snapshot to restore: (or press Back) │
│ >_ │
│ │
└────────────────────────────────────────────────────────┘
# After selecting snapshot:
┌─ Restore zroot@manual-20260323_1842 ──────────────────┐
│ │
│ ⚠ WARNING: This will rollback ZFS to the snapshot │
│ point. All changes since creation will be lost. │
│ │
│ Created: 2026-03-23 18:42 UTC │
│ Size: 1.8 GB │
│ Systems affected: postgres, apps │
│ │
│ [ Confirm Restore ] [ Cancel ] │
│ │
└────────────────────────────────────────────────────────┘
Logs Submenu
┌─ System Logs ──────────────────────────────────────────┐
│ │
│ View logs from: │
│ [ Clawdie Service (syslog) ] │
│ [ Clawdie Service (stdout/stderr) ] │
│ [ Worker Jail Console ] │
│ [ Database Jail Console ] │
│ [ System Messages (/var/log/messages) ] │
│ │
│ [ Back ] │
│ │
└────────────────────────────────────────────────────────┘
# After selecting log:
┌─ Clawdie Service Log (last 50 lines) ──────────────────┐
│ │
│ [2026-03-23 23:15:22] Agent startup complete │
│ [2026-03-23 23:15:21] Jails ready: worker, db, cms │
│ [2026-03-23 23:15:18] PostgreSQL health check: OK │
│ [2026-03-23 23:15:15] Nginx config validated │
│ ... │
│ [2026-03-23 23:15:01] rc.d clawdie service start │
│ │
│ [ Refresh ] [ Tail (follow mode) ] [ Back ] │
│ │
└────────────────────────────────────────────────────────┘
Config Submenu
┌─ Configuration ────────────────────────────────────────┐
│ │
│ Current Settings │
│ ├─ Assistant Name: Clawdie │
│ ├─ Domain: clawdie.local │
│ ├─ Timezone: Europe/Ljubljana │
│ ├─ LLM Provider: Anthropic │
│ └─ Telegram: ✓ Configured │
│ │
│ [ Change Assistant Name ] │
│ [ Change Timezone ] │
│ [ Change LLM Provider ] │
│ [ Export .env ] │
│ [ View .env (sensitive) ] │
│ [ Back ] │
│ │
└────────────────────────────────────────────────────────┘
Implementation (clawdie-admin.sh)
File Locations
/usr/local/bin/clawdie-admin ← wrapper script (user-callable)
/usr/local/libexec/clawdie-admin.sh ← main menu logic (sourced)
/usr/local/libexec/clawdie-lib-admin.sh ← helper functions (jail, snap, log ops)
Wrapper Script
#!/bin/sh
# /usr/local/bin/clawdie-admin
# Make sure we're running as clawdie user (or via sudo for root ops)
if [ "$(id -u)" != 1001 ]; then
# If run as root, switch to clawdie user
if [ "$(id -u)" = 0 ]; then
exec su - clawdie -c "clawdie-admin"
fi
echo "Error: must run as clawdie user or root"
exit 1
fi
# Source main menu
. /usr/local/libexec/clawdie-admin.sh
# Start menu
clawdie_admin_main_menu
Main Menu Function
#!/bin/sh
# /usr/local/libexec/clawdie-admin.sh
. /usr/local/libexec/clawdie-lib-admin.sh
clawdie_admin_main_menu() {
while true; do
# Refresh status each iteration
STATUS=$(clawdie_admin_quick_status)
CHOICE=$(bsddialog --title "Clawdie Admin Panel" \
--menu "Quick Status:\n$STATUS" 20 70 0 \
"1" "System Health" \
"2" "Jails" \
"3" "Snapshots" \
"4" "Logs" \
"5" "Configuration" \
"0" "Quit" \
2>&1 >/dev/tty)
case $CHOICE in
1) clawdie_admin_health ;;
2) clawdie_admin_jails ;;
3) clawdie_admin_snapshots ;;
4) clawdie_admin_logs ;;
5) clawdie_admin_config ;;
0) break ;;
*) ;;
esac
done
}
clawdie_admin_quick_status() {
# Returns 4-line status string for main menu display
# Check if clawdie service running
if service clawdie status > /dev/null 2>&1; then
PID=$(service clawdie status | grep -oP 'pid \K\d+' || echo "?")
SERVICE_STATUS="✓ Running (pid $PID)"
else
SERVICE_STATUS="✗ Stopped"
fi
# ZFS pool usage
POOL_USAGE=$(zfs list -H -o used,available zroot 2>/dev/null | awk '{
used = $1 / 1024 / 1024 / 1024
avail = $2 / 1024 / 1024 / 1024
total = used + avail
pct = int(used / total * 100)
printf "%.1f GB / %.1f GB (%d%%)", used, total, pct
}')
# Memory usage
MEM=$(free -m | grep "^Mem:" | awk '{
used = $3
total = $2
pct = int(used / total * 100)
printf "%.1f GB / %.1f GB (%d%%)", used/1024, total/1024, pct
}')
# CPU load
LOAD=$(uptime | sed 's/.*load average: //' | awk '{print $1}')
echo "Clawdie: $SERVICE_STATUS"
echo "ZFS pool (zroot): $POOL_USAGE"
echo "Memory: $MEM"
echo "CPU Load: $LOAD (1 min)"
}
Helper Library Functions
#!/bin/sh
# /usr/local/libexec/clawdie-lib-admin.sh
# Jail operations
clawdie_jail_list() {
jls -N name hostname state ip4.addr 2>/dev/null | grep -E "^(worker|db|cms|mgmt)" || echo "No jails found"
}
clawdie_jail_status() {
local jail=$1
jls -j "$jail" state 2>/dev/null || echo "unknown"
}
clawdie_jail_start() {
local jail=$1
service jail onestart "$jail" 2>&1 | tail -5
}
clawdie_jail_stop() {
local jail=$1
service jail onestop "$jail" 2>&1 | tail -5
}
clawdie_jail_console() {
local jail=$1
jexec "$jail" /bin/sh
}
# ZFS operations
clawdie_snapshot_create() {
local label=$1
local snapshot="zroot@manual-$(date +%Y%m%d_%H%M)"
zfs snapshot "$snapshot" 2>&1
echo "Created: $snapshot"
}
clawdie_snapshot_list() {
zfs list -t snapshot -r zroot 2>/dev/null | grep "^zroot" || echo "No snapshots"
}
clawdie_snapshot_restore() {
local snapshot=$1
bsddialog --title "Confirm Restore" \
--yesno "Restore $snapshot? This will rollback all changes since creation." 0 0
if [ $? -eq 0 ]; then
zfs rollback "$snapshot" 2>&1 | head -20
echo "Rollback complete. System restart recommended."
fi
}
# Log operations
clawdie_log_service() {
tail -50 /var/log/clawdie.log 2>/dev/null || echo "No log found"
}
clawdie_log_jail() {
local jail=$1
jexec "$jail" tail -50 /var/log/messages 2>/dev/null || echo "No jail log"
}
# Config operations
clawdie_config_read_env() {
# Read current .env without exposing secrets
grep -E "^(ASSISTANT_NAME|AGENT_DOMAIN|TZ|LLM_PROVIDER)" \
/home/clawdie/clawdie-ai/.env 2>/dev/null || echo "Config not found"
}
clawdie_config_change_name() {
bsddialog --title "Change Assistant Name" \
--inputbox "Enter new assistant name:" 0 0 "$CURRENT_NAME" 2>&1 | \
xargs -I {} sed -i '' "s/^ASSISTANT_NAME=.*/ASSISTANT_NAME={}/" \
/home/clawdie/clawdie-ai/.env
}
Usage Examples
Example 1: Start Worker Jail
# User: Jails → select "1" (worker) → [Start]
clawdie_jail_start worker
# Output: "Jail worker started"
Example 2: View Recent Logs
# User: Logs → [Clawdie Service] → shows tail -50 /var/log/clawdie.log
tail -50 /var/log/clawdie.log
Example 3: Create Manual Snapshot
# User: Snapshots → [Create Snapshot]
clawdie_snapshot_create "manual"
# Output: "Created: zroot@manual-20260323_2145"
Desktop Integration
Tray Applet
Binary: /usr/local/libexec/clawdie-tray-applet (shell script)
#!/bin/sh
# Updates tray icon every 5 seconds
RUNNING_ICON="/usr/local/share/icons/clawdie-running.png"
STOPPED_ICON="/usr/local/share/icons/clawdie-stopped.png"
while true; do
if service clawdie status > /dev/null 2>&1; then
echo "ICON:$RUNNING_ICON"
echo "LABEL:Clawdie"
else
echo "ICON:$STOPPED_ICON"
echo "LABEL:Clawdie (offline)"
fi
sleep 5
done
Right-click binding: Exec=/usr/local/bin/clawdie-admin
Permissions & Security
User (clawdie) Operations
- Jail status queries: via
jls(unprivileged) - Logs: read
/var/log/clawdie.log(user-owned) - Config: read
.env(user-owned)
Privileged Operations (via sudo)
- Jail start/stop: requires root via
sudo service jail ... - ZFS snapshot/rollback: requires root via
sudo zfs ... - System restart: requires root via
sudo shutdown ...
sudo Setup (automatic in firstboot.sh):
# /usr/local/etc/sudoers.d/clawdie-admin
# Clawdie admin operations
%wheel ALL=(ALL) NOPASSWD: /usr/local/libexec/clawdie-lib-admin.sh
%wheel ALL=(ALL) NOPASSWD: /usr/sbin/service
%wheel ALL=(ALL) NOPASSWD: /usr/sbin/zfs
%wheel ALL=(ALL) NOPASSWD: /usr/sbin/jail
%wheel ALL=(ALL) NOPASSWD: /usr/bin/killall
Accessibility
- Keyboard navigation: bsddialog supports tab/arrow keys, Enter
- High contrast: Inherits the active desktop theme settings
- No mouse required: All functions accessible via keyboard
- Screen reader: Standard bsddialog output readable by screen readers
Performance Considerations
- Startup: <200 ms (pure shell, no Java/Python startup overhead)
- Refresh rate: 5-second poll for status (minimal CPU)
- Memory: <5 MB footprint
- Concurrency: Lock file prevents multiple instances (
/tmp/clawdie-admin.lock)
Testing Checklist
clawdie-adminlaunches from the desktop tray or launcherclawdie-adminlaunches from terminal- System Health displays correct CPU/memory/disk stats
- Jails submenu lists all jails (worker, db, cms)
- Can toggle jail start/stop with sudo confirmation
- Can open jail console via jexec
- Snapshots create/list/restore work with sudo
- Logs display correctly (tailed, not truncated)
- Config display matches current .env
- No secrets logged to console
- Multiple concurrent instances prevented (lock file)
- Menu navigation works with keyboard only
Future Enhancements
- Backup scheduling UI — configure daily/weekly backups
- Network config — change IP addresses, DNS
- Service management — start/stop nginx, PostgreSQL within jails
- Metrics export — export system stats to Grafana
- Terminal multiplexing — tmux split for multiple jails open simultaneously