diff --git a/wizards/bezalel/emacs-daemon-start.sh b/wizards/bezalel/emacs-daemon-start.sh new file mode 100755 index 00000000..33de1cad --- /dev/null +++ b/wizards/bezalel/emacs-daemon-start.sh @@ -0,0 +1,31 @@ +#!/bin/bash +set -euo pipefail + +# Emacs daemon startup script for Bezalel +# Idempotent: safe to run via cron @reboot + +SOCKET_DIR="/srv/fleet/emacs/socket" +LOG_DIR="/srv/fleet/logs" +DAEMON_NAME="bezalel" +EMACS_BIN="${EMACS_BIN:-emacs}" +CONFIG_FILE="/root/wizards/bezalel/emacs-daemon.el" + +# Create shared socket directory with group write access +if [ ! -d "$SOCKET_DIR" ]; then + mkdir -p "$SOCKET_DIR" + chmod 2775 "$SOCKET_DIR" + chgrp fleet "$SOCKET_DIR" 2>/dev/null || true +fi + +# Create audit log directory +mkdir -p "$LOG_DIR" + +# Start daemon if not already running +if ! emacsclient -s "$DAEMON_NAME" -e "(+ 1 1)" >/dev/null 2>&1; then + $EMACS_BIN --daemon="$DAEMON_NAME" \ + --socket-dir="$SOCKET_DIR" \ + -l "$CONFIG_FILE" + echo "$(date): Emacs daemon '$DAEMON_NAME' started" +else + echo "$(date): Emacs daemon '$DAEMON_NAME' already running" +fi diff --git a/wizards/bezalel/emacs-daemon.el b/wizards/bezalel/emacs-daemon.el new file mode 100644 index 00000000..10809adb --- /dev/null +++ b/wizards/bezalel/emacs-daemon.el @@ -0,0 +1,61 @@ +;; Emacs daemon configuration for Bezalel +;; Shared socket + audit trail + +(setq server-name "bezalel") +(setq server-socket-dir "/srv/fleet/emacs/socket") +(setq server-socket-mod-group-permissions t) ; group-accessible socket + +;; Ensure audit log directory exists +(let ((log-dir "/srv/fleet/logs")) + (unless (file-directory-p log-dir) + (make-directory log-dir t))) + +;; Audit log file +(defconst bezalel-audit-log "/srv/fleet/logs/emacs-audit.log" + "Audit log for all emacsclient eval operations.") + +(defun bezalel-log-audit (user expression result status) + "Log an audit entry for an eval operation." + (let ((timestamp (format-time-string "%Y-%m-%dT%H:%M:%S%z")) + (entry (format "{\"timestamp\":\"%s\",\"user\":\"%s\",\"expression\":\"%s\",\"result\":\"%s\",\"status\":\"%s\"}\n" + timestamp user expression result status))) + (with-temp-file bezalel-audit-log + (goto-char (point-max)) + (insert entry)))) + +;; Capture the connecting user's identity +(defvar bezalel-connection-user nil + "User identity from the Emacs client connection.") + +;; Use environment variables set by emacsclient wrapper or cron +(setq bezalel-connection-user + (or (getenv "SUDO_USER") + (getenv "USER") + (getenv "LOGNAME") + "unknown")) + +;; Wrap server-eval-and-print with audit logging +(defun bezalel-audited-server-eval-and-print (exp) + "Evaluate EXP and print result, logging to audit trail." + (let* (result + status-ok + (result-raw (condition-case err + (progn + (setq result (prin1-to-string (eval exp))) + (setq status-ok t) + result) + (error + (setq status-ok nil) + (format "ERROR: %s" (error-message-string err)))))) + (bezalel-log-audit bezalel-connection-user + (prin1-to-string exp) + result-raw + (if status-ok "ok" "error")) + (if status-ok + result + (error "%s" result-raw)))) + +;; Replace the standard handler with our audited version +(advice-add 'server-eval-and-print :override #'bezalel-audited-server-eval-and-print) + +(provide 'emacs-daemon)