Compare commits

...

1 Commits

Author SHA1 Message Date
Alexander Payne
666ff65ac6 [EMACS] Fix daemon socket + per-user identity and audit trail on Bezalel
Some checks failed
Architecture Lint / Linter Tests (pull_request) Successful in 29s
Smoke Test / smoke (pull_request) Failing after 24s
Validate Config / YAML Lint (pull_request) Failing after 20s
Validate Config / JSON Validate (pull_request) Successful in 17s
Validate Config / Python Syntax & Import Check (pull_request) Failing after 52s
Validate Config / Shell Script Lint (pull_request) Failing after 51s
Validate Config / Python Test Suite (pull_request) Has been skipped
Validate Config / Deploy Script Dry Run (pull_request) Successful in 12s
Validate Config / Cron Syntax Check (pull_request) Successful in 13s
Validate Config / Playbook Schema Validation (pull_request) Successful in 28s
Architecture Lint / Lint Repository (pull_request) Failing after 15s
PR Checklist / pr-checklist (pull_request) Successful in 3m24s
Add Ansible role to deploy a shared Emacs daemon with:

- Named socket "bezalel" in group-accessible /srv/fleet/emacs/sockets
- Socket directory created with mode 2775 (setgid) so all fleet group members can connect
- Socket file permissions set to 660 via server-socket-permissions
- Per-user identity: client wrapper logs invoking user to /srv/fleet/logs/emacs-audit.log and sets EMACS_USER env
- Server-side audit: after-save-hook logs file writes with user and filename
- Connection logging via both wrapper and server hook
- Systemd unit ensures daemon starts and restarts on failure
- Conditional deployment (only on bezalel) via site.yml

Acceptance criteria:
- emacsclient -s bezalel -e "(+ 1 1)" returns 2
- Audit log shows distinct users (timmy, alexander, etc.) for connections and file writes
- No unilateral root edits without trace

Closes #429
2026-04-29 00:46:08 -04:00
7 changed files with 218 additions and 0 deletions

View File

@@ -46,6 +46,10 @@
- role: cron_manager - role: cron_manager
tags: [cron, schedule] tags: [cron, schedule]
- role: emacs
when: wizard_name == "Bezalel"
tags: [emacs, daemon]
post_tasks: post_tasks:
- name: "Final validation — scan for banned providers" - name: "Final validation — scan for banned providers"
shell: | shell: |

View File

@@ -0,0 +1,26 @@
# emacs Ansible Role
Installs and configures a shared Emacs daemon for the Bezalel VPS.
## What it does
- Ensures `fleet` group exists
- Installs Emacs (if needed)
- Creates `/srv/fleet/emacs/sockets` (mode 2775, group fleet)
- Creates `/srv/fleet/logs` (mode 2775)
- Touches `/srv/fleet/logs/emacs-audit.log` (mode 0664)
- Deploys `/root/.emacs.d/init.el` with:
- Socket dir set to shared location
- Group-accessible socket (#o660)
- Audit hooks for file saves and client connections
- Deploys `/usr/local/bin/emacsclient-bezalel` wrapper that logs caller identity
- Deploys systemd unit `emacs-bezalel.service` and starts it
## Usage
The role is automatically included site-wide when `wizard_name == 'bezalel'`.
## Acceptance
- `emacsclient -s bezalel -e "(+ 1 1)"` should print `2` and return exit 0
- `/srv/fleet/logs/emacs-audit.log` should contain user=... entries for each client and file write

View File

@@ -0,0 +1,8 @@
---
# Handlers for the emacs role
- name: Restart emacs-bezalel
systemd:
name: emacs-bezalel
state: restarted
daemon_reload: yes

View File

@@ -0,0 +1,92 @@
---
# =============================================================================
# emacs — Shared Emacs daemon with multi-user socket and audit trail
# =============================================================================
# Deploys and configures Emacs as a daemon on the VPS with:
# - Named socket "bezalel" in /srv/fleet/emacs/sockets
# - Group-writable socket for fleet access
# - Audit logging for connections and file writes
# - Per-user identity via EMACS_USER env
# =============================================================================
- name: "Ensure fleet group exists (idempotent)"
group:
name: fleet
state: present
- name: "Ensure Emacs package installed"
package:
name: emacs
state: present
when: machine_type == 'vps'
ignore_errors: true # not critical if emacs already present
- name: "Create Emacs socket directory"
file:
path: /srv/fleet/emacs/sockets
state: directory
mode: '2775'
group: fleet
owner: root
recurse: true
- name: "Create Emacs logs directory"
file:
path: /srv/fleet/logs
state: directory
mode: '2775'
group: fleet
owner: root
recurse: true
- name: "Ensure audit log file exists with group write"
file:
path: /srv/fleet/logs/emacs-audit.log
state: touch
mode: '0664'
group: fleet
owner: root
- name: "Create root .emacs.d directory if missing"
file:
path: /root/.emacs.d
state: directory
mode: '0755'
owner: root
group: root
- name: "Deploy Emacs init.el configuration"
template:
src: init.el.j2
dest: /root/.emacs.d/init.el
mode: '0644'
owner: root
group: root
notify: Restart emacs-bezalel
- name: "Deploy emacsclient wrapper script"
template:
src: client-wrapper.sh.j2
dest: /usr/local/bin/emacsclient-bezalel
mode: '0755'
owner: root
group: root
- name: "Deploy systemd unit for Emacs daemon"
template:
src: emacs-bezalel.service.j2
dest: /etc/systemd/system/emacs-bezalel.service
mode: '0644'
owner: root
group: root
notify: Restart emacs-bezalel
- name: "Reload systemd to pick up new unit"
systemd:
daemon_reload: yes
- name: "Ensure Emacs daemon is enabled and started"
systemd:
name: emacs-bezalel
enabled: true
state: started

View File

@@ -0,0 +1,19 @@
#!/bin/bash
# Emacs client wrapper for Bezalel — logs identity and delegates to emacsclient
# This script must be installed setuid root? No — just group-executable.
# AUDIT log: /srv/fleet/logs/emacs-audit.log
AUDIT_LOG="/srv/fleet/logs/emacs-audit.log"
USERNAME="$(whoami)"
TIMESTAMP="$(date -Iseconds)"
# Log the client connection attempt (pre-connect)
echo "${TIMESTAMP} user=${USERNAME} action=emacsclient-connect args=$*" >> "${AUDIT_LOG}"
# Pass the username into the Emacs server environment so it can
# appear in server-side audit entries as well.
export EMACS_USER="${USERNAME}"
# Delegate to the real emacsclient, preserving all args.
# The socket name 'bezalel' is configured by server-name in init.el.
exec /usr/bin/emacsclient -s bezalel "$@"

View File

@@ -0,0 +1,16 @@
[Unit]
Description=Emacs daemon for Bezalel (shared fleet service)
After=network-online.target
Wants=network-online.target
[Service]
Type=forking
ExecStart=/usr/bin/emacs --daemon=bezalel
ExecStop=/usr/bin/emacsclient -s bezalel -e "(progn (setq kill-emacs-hook nil) (kill-emacs))"
Restart=on-failure
RestartSec=10
# Ensure HOME is set correctly
Environment=HOME=/root
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,53 @@
;; Emacs init for Bezalel daemon — shared fleet service
;; Managed by Ansible. DO NOT EDIT MANUALLY.
;; Last updated: {{ ansible_date_time.iso8601 }}
;; -------------------- Socket configuration --------------------
;; Use a shared, group-accessible socket directory
(setq server-socket-dir "/srv/fleet/emacs/sockets/")
;; Ensure the socket directory exists with setgid so new files inherit group 'fleet'
(let ((dir server-socket-dir))
(unless (file-directory-p dir)
(make-directory dir t))
(set-file-modes dir #o2775))
;; Socket file permissions: group read/write so all fleet members can connect
(setq server-socket-permissions #o660)
;; Name this daemon instance "bezalel" (matches systemd --daemon=bezalel)
(setq server-name "bezalel")
;; -------------------- Audit trail --------------------
(defvar emacs-audit-log "/srv/fleet/logs/emacs-audit.log"
"Path to the fleet audit log for Emacs operations.")
(defun emacs-audit-log-entry (operation file &optional user)
"Append an audit entry for OPERATION on FILE by USER.
USER defaults to EMACS_USER env var (set by client wrapper) or
the daemon's user-login-name."
(let ((user (or user
(getenv "EMACS_USER")
(user-login-name))))
(with-temp-buffer
(insert (format "%s user=%s operation=%s file=%s\n"
(format-time-string "%Y-%m-%dT%H:%M:%S%z")
user
operation
(expand-file-name file)))
(write-region (point-min) (point-max) emacs-audit-log 'append))))
;; Log every file buffer save
(add-hook 'after-save-hook
(lambda ()
(when buffer-file-name
(emacs-audit-log-entry "write" buffer-file-name))))
;; Also log client connections (best-effort via EMACS_USER)
(add-hook 'server-after-make-frame-hook
(lambda ()
(let ((client (frame-parameter nil 'client)))
(when client
(emacs-audit-log-entry "connect" "emacsclient" (getenv "EMACS_USER"))))))
(provide 'init)