feat: daemon mode with named socket, scripts/run for IP:PORT

This commit is contained in:
2026-06-20 15:28:53 -04:00
parent 8ee1c261e7
commit 5b46999b3c
3 changed files with 78 additions and 10 deletions
@@ -17,16 +17,18 @@ simple (one language, two packages) and is adequate for Spine's complexity level
|---------|---------|--------|---------| |---------|---------|--------|---------|
| `simple-httpd` | MELPA latest | MELPA | HTTP server, `defservlet` routing | | `simple-httpd` | MELPA latest | MELPA | HTTP server, `defservlet` routing |
| `mustache` | MELPA latest | MELPA | Mustache template rendering | | `mustache` | MELPA latest | MELPA | Mustache template rendering |
| `ht` | — | transitive via `mustache` | Hash table for template context | | `ht` | MELPA latest | MELPA | Hash table for template context |
## Project structure ## Project structure
``` ```
spine/ spine/
├── spine.el # entry point: bootstrap, server start, handlers ├── scripts/
│ └── run # start the server as a daemon
├── spine.el # entry point: bootstrap, server start, handlers
├── templates/ ├── templates/
│ └── hello.mustache # smoke-test template │ └── hello.mustache # smoke-test template
├── spine-spec.md # design brief (existing) ├── spine-spec.md # design brief (existing)
├── spine-mockup-a-agenda.html ├── spine-mockup-a-agenda.html
├── spine-mockup-b-journal.html ├── spine-mockup-b-journal.html
└── README.md └── README.md
@@ -45,7 +47,7 @@ packages from MELPA; subsequent starts skip the install step.
(require 'package) (require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
(package-initialize) (package-initialize)
(dolist (pkg '(simple-httpd mustache)) (dolist (pkg '(simple-httpd mustache ht))
(unless (package-installed-p pkg) (unless (package-installed-p pkg)
(package-refresh-contents) (package-refresh-contents)
(package-install pkg))) (package-install pkg)))
@@ -60,16 +62,24 @@ start.
(defvar spine-port 8080 (defvar spine-port 8080
"Port for the Spine HTTP server.") "Port for the Spine HTTP server.")
(defvar spine-host nil
"Host for the Spine HTTP server.
nil means localhost only. Set to a string like \"0.0.0.0\" to listen on
all interfaces.")
(defvar spine-template-dir (defvar spine-template-dir
(expand-file-name "templates/" (file-name-directory load-file-name)) (expand-file-name "templates/" (file-name-directory load-file-name))
"Directory containing .mustache templates.") "Directory containing .mustache templates.")
(setq httpd-host spine-host)
(setq httpd-port spine-port) (setq httpd-port spine-port)
(httpd-start) (httpd-start)
(message "Spine listening on http://localhost:%d" spine-port) (message "Spine listening on http://%s:%d"
(or spine-host "localhost") spine-port)
``` ```
- Port defaults to 8080, overridable by setting `spine-port` before loading. - Port defaults to 8080, overridable via `spine-port`.
- Host defaults to nil (localhost-only), overridable via `spine-host`.
- `spine-template-dir` resolves relative to `spine.el`'s directory so the app - `spine-template-dir` resolves relative to `spine.el`'s directory so the app
works regardless of `default-directory`. works regardless of `default-directory`.
@@ -124,6 +134,23 @@ Returns the rendered string."
## Invocation ## Invocation
Production use — Emacs daemon with a named socket, no GUI frame:
```bash
scripts/run [HOST:]PORT
```
- `scripts/run 9090``http://localhost:9090/hello`
- `scripts/run 0.0.0.0:8080``http://0.0.0.0:8080/hello` (all interfaces)
- `scripts/run``http://localhost:8080/hello`
Stop: `emacsclient --socket-name=spine --eval '(kill-emacs)'`
The socket name `spine` is separate from the default Emacs daemon socket,
so your personal daemon is unaffected.
Development use — interactive Emacs in the terminal:
```bash ```bash
emacs --quick --load spine.el emacs --quick --load spine.el
``` ```
@@ -141,5 +168,4 @@ emacs --quick --load spine.el
- Book data, Org file integration, any handler beyond `/hello`. - Book data, Org file integration, any handler beyond `/hello`.
- Static file serving (CSS, JS, icons). - Static file serving (CSS, JS, icons).
- Emacs daemon mode, process management, systemd units.
- Tests — the scaffold is small enough to smoke-test manually. - Tests — the scaffold is small enough to smoke-test manually.
Executable
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
HOST=""
PORT="8080"
if [ $# -ge 1 ] && [ -n "$1" ]; then
ARG="$1"
if [[ "$ARG" == *:* ]]; then
HOST="${ARG%:*}"
PORT="${ARG##*:}"
else
PORT="$ARG"
fi
fi
# Stop any existing spine daemon
emacsclient --socket-name=spine --eval '(kill-emacs)' 2>/dev/null || true
# Build emacs args
EMACS_ARGS=(emacs --quick --daemon=spine)
if [ -n "$HOST" ]; then
EMACS_ARGS+=(--eval "(setq spine-host \"$HOST\")")
fi
EMACS_ARGS+=(--eval "(setq spine-port $PORT)")
EMACS_ARGS+=(--load "$ROOT_DIR/spine.el")
"${EMACS_ARGS[@]}"
DISPLAY_HOST="${HOST:-localhost}"
echo "Spine running at http://${DISPLAY_HOST}:${PORT}/hello"
echo "Stop: emacsclient --socket-name=spine --eval '(kill-emacs)'"
+7 -2
View File
@@ -21,6 +21,10 @@
(defvar spine-port 8080 (defvar spine-port 8080
"Port for the Spine HTTP server.") "Port for the Spine HTTP server.")
(defvar spine-host nil
"Host for the Spine HTTP server.
nil means localhost only. Set to a string like \"0.0.0.0\" to listen on
all interfaces.")
(defvar spine-template-dir (defvar spine-template-dir
(expand-file-name "templates/" (expand-file-name "templates/"
@@ -47,10 +51,11 @@ Returns the rendered string."
("message" "Hello from Emacs."))))) ("message" "Hello from Emacs.")))))
;; --- start ------------------------------------------------------------ ;; --- start ------------------------------------------------------------
(setq httpd-host spine-host)
(setq httpd-port spine-port) (setq httpd-port spine-port)
(httpd-start) (httpd-start)
(message "Spine listening on http://localhost:%d" spine-port) (message "Spine listening on http://%s:%d"
(or spine-host "localhost") spine-port)
(provide 'spine) (provide 'spine)
;;; spine.el ends here ;;; spine.el ends here