Here are a couple techniques I use for setting up a CL website that runs at boot time. They're not especially generic or reusable as-is, but I hope the ideas are useful.

Booting: Here's how I make sure l1sp.org starts when I reboot my Linux box.

/etc/rc.local:

su xach -c 'screen -d -m -S l1sp -c /opt/l1sp/etc/screenrc'

/opt/l1sp/etc/screenrc:

setenv SBCL_HOME "/opt/l1sp/lib/sbcl" setenv PATH "/opt/l1sp/bin:${PATH}" screen /opt/l1sp/bin/l1sp --load /opt/l1sp/etc/init.lisp screen emacs -q -l /opt/l1sp/etc/init.el screen tail -f /var/log/nginx/l1sp.org.log chdir /opt/l1sp

/opt/l1sp/etc/init.lisp:

(defpackage #:l1sp-init (:use #:cl)) (in-package #:l1sp-init) (defvar *swank-port* 7717) (defvar *tbnl-port* 7718) (defvar *tbnl-server*) (require 'redirector) (redirector:initialize) (swank:create-server :port *swank-port* :dont-close t) (setf *tbnl-server* (tbnl:start-server :port *tbnl-port*))

/opt/l1sp/etc/init.el:

(require 'cl) ;;; elisp setup (defun xach-generic-code-setup () (setq indent-tabs-mode nil) (local-set-key "\C-m" 'newline-and-indent)) (add-hook 'emacs-lisp-mode-hook (defun xach-emacs-setup () (interactive) (xach-generic-code-setup) (eldoc-mode t))) (setq indent-tabs-mode nil) ;;; CL setup (push "/opt/l1sp/src/slime" load-path) (require 'slime) (slime-setup '(slime-autodoc slime-fancy-inspector slime-editing-commands)) (setq slime-port 7717) (add-hook 'lisp-mode-hook (defun xach-lisp-setup () (interactive) (xach-generic-code-setup))) (global-set-key "\C-cs" 'slime-selector)

Customizing: Here's how I make the customized /opt/l1sp/bin/l1sp executable. This is the nth of m iterations an idea I've been using for a while.

customizer.lisp:

;;;; customizer.lisp (require :asdf) (require :sb-posix) (defpackage #:sbcl-customizer (:use #:cl)) (in-package #:sbcl-customizer) (defparameter *customize-system-directory* (probe-file (sb-posix:getenv "CUSTOMIZE_DIR"))) (defun bomb-out (condition hook-value) (declare (ignore hook-value)) (format *error-output* "~&unhandled ~S:~2%~4T~A~2%" (type-of condition) condition) (sb-ext:quit :unix-status 1)) (setf sb-ext:*invoke-debugger-hook* 'bomb-out) (defun load-if-present (file) (when (probe-file file) (format *trace-output* "~&;; loading ~A~%" file) (load file))) (defun pathname-system-name (pathname) "What system does PATHNAME end with? E.g. #\"/foo/bar/baz/\" => \"baz\"." (first (last (pathname-directory (pathname pathname))))) (defun customize-file-name (system-name filename) (merge-pathnames (make-pathname :directory (list :relative system-name) :type "lisp" :name filename) *customize-system-directory*)) (defun all-customize-files (system-name) (let* ((pathname (customize-file-name system-name :wild)) (files (directory pathname))) (remove-if (lambda (name) (member name '("pre" "post") :test #'string=)) files :key #'pathname-name))) (defun customize (system-name) (let ((pre (customize-file-name system-name "pre")) (post (customize-file-name system-name "post"))) (load-if-present pre) ;; Ugh. This FIND-SYSTEM is mostly so asdf can be customized, even ;; though you can't asdf:load-op it. (unless (find-package (string-upcase system-name)) (asdf:oos 'asdf:load-op system-name)) (dolist (file (all-customize-files system-name)) (load-if-present file)) (load-if-present post))) (defun customized-systems (pathname) (let ((files (directory (merge-pathnames "*/*.lisp" pathname)))) (remove-duplicates (mapcar #'pathname-system-name files) :test #'string=))) (defun main () (let ((systems (customized-systems *customize-system-directory*))) (load-if-present (merge-pathnames "global.lisp" *customize-system-directory*)) (dolist (system systems) (customize system)) (sb-ext:without-package-locks (setf (fdefinition 'sb-int:sbcl-homedir-pathname) (constantly (sb-posix:getenv "NEW_SBCL_HOME")))) (setf sb-impl::*sysinit-pathname-function* (constantly nil)) (setf sb-impl::*userinit-pathname-function* (constantly nil)) (setf sb-ext:*invoke-debugger-hook* nil) (sb-ext:save-lisp-and-die (sb-posix:getenv "OUTPUT_FILE") :executable t))) (main)

customizer.sh:

#!/bin/bash function usage () { echo "customizer.sh SBCL COREFILE CUSTOMIZE-DIR OUTPUT-FILE NEW-SBCL-HOME" exit 1; } function check_arg () { if [ -z "$1" ];then usage fi } SBCL=$1 CORE=$2 CUSTOMIZE_DIR=$3 OUTPUT_FILE=$4 NEW_SBCL_HOME=$5 check_arg "$SBCL" check_arg "$CORE" check_arg "$CUSTOMIZE_DIR" check_arg "$OUTPUT_FILE" check_arg "$NEW_SBCL_HOME" export CUSTOMIZE_DIR OUTPUT_FILE NEW_SBCL_HOME $SBCL --core $CORE \ --disable-debugger \ --userinit /dev/null \ --sysinit /dev/null \ --load "customizer.lisp"

Here's the tree of customization files I load for l1sp.org:

swank/pre.lisp html-template/no-warnings.lisp asdf/recompile-stale.lisp asdf/misc-useful.lisp asdf/l1sp-registry.lisp global.lisp hunchentoot/post.lisp

They do things like set up a fixed location for l1sp asdf systems, turn off html-template warnings, etc.