diff --git a/docs/specs/2026-06-20-initial-scaffold-design.md b/docs/specs/2026-06-20-initial-scaffold-design.md index a067d88..d14247e 100644 --- a/docs/specs/2026-06-20-initial-scaffold-design.md +++ b/docs/specs/2026-06-20-initial-scaffold-design.md @@ -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 | | `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 ``` 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/ -│ └── hello.mustache # smoke-test template -├── spine-spec.md # design brief (existing) +│ └── hello.mustache # smoke-test template +├── spine-spec.md # design brief (existing) ├── spine-mockup-a-agenda.html ├── spine-mockup-b-journal.html └── README.md @@ -45,7 +47,7 @@ packages from MELPA; subsequent starts skip the install step. (require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (package-initialize) -(dolist (pkg '(simple-httpd mustache)) +(dolist (pkg '(simple-httpd mustache ht)) (unless (package-installed-p pkg) (package-refresh-contents) (package-install pkg))) @@ -60,16 +62,24 @@ start. (defvar spine-port 8080 "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 (expand-file-name "templates/" (file-name-directory load-file-name)) "Directory containing .mustache templates.") +(setq httpd-host spine-host) (setq httpd-port spine-port) (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 works regardless of `default-directory`. @@ -124,6 +134,23 @@ Returns the rendered string." ## 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 emacs --quick --load spine.el ``` @@ -141,5 +168,4 @@ emacs --quick --load spine.el - Book data, Org file integration, any handler beyond `/hello`. - Static file serving (CSS, JS, icons). -- Emacs daemon mode, process management, systemd units. - Tests — the scaffold is small enough to smoke-test manually. diff --git a/scripts/run b/scripts/run new file mode 100755 index 0000000..36408df --- /dev/null +++ b/scripts/run @@ -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)'" diff --git a/spine.el b/spine.el index 8cdee29..09b6275 100644 --- a/spine.el +++ b/spine.el @@ -21,6 +21,10 @@ (defvar spine-port 8080 "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 (expand-file-name "templates/" @@ -47,10 +51,11 @@ Returns the rendered string." ("message" "Hello from Emacs."))))) ;; --- start ------------------------------------------------------------ - +(setq httpd-host spine-host) (setq httpd-port spine-port) (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) ;;; spine.el ends here