diff --git a/.gitignore b/.gitignore
index 4216ba1d..2c93fe1f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,9 @@
-.direnv/
-node_modules/
-output_*/
-source/build/
-vendor/
+# Do not edit this file. It is automatically generated by https://www.oliverdavies.uk/build-configs.
+
+/output_*/
+/vendor/
+
+/.direnv/
+
+/node_modules/
+/source/build/
diff --git a/.tmux b/.tmux
index ee94e4b1..c27c952f 100755
--- a/.tmux
+++ b/.tmux
@@ -16,7 +16,7 @@ tmux new-session -d -s "${session_name}" -n vim -c "${session_path}"
 # 1. Main window: Vim
 tmux send-keys -t "${session_name}:vim" "nvim" Enter
 tmux split-pane -t "${session_name}:vim" -h -c "${session_path}" -p 40
-tmux send-keys -t "${session_name}:vim.right" "vendor/bin/sculpin generate --server --watch" Enter
+tmux send-keys -t "${session_name}:vim.right" "./run start" Enter
 
 # 2. General shell use.
 tmux new-window -t "${session_name}" -c "${session_path}"
diff --git a/build.yaml b/build.yaml
new file mode 100644
index 00000000..d692da44
--- /dev/null
+++ b/build.yaml
@@ -0,0 +1,16 @@
+name: oliverdavies-uk
+type: sculpin
+language: php
+
+flake:
+  devshell:
+    packages:
+      - nodePackages.pnpm
+      - nodejs
+      - php81
+      - php81Packages.composer
+
+git:
+  ignore:
+    - /node_modules/
+    - /source/build/
diff --git a/flake.lock b/flake.lock
index ec727d9d..58815101 100644
--- a/flake.lock
+++ b/flake.lock
@@ -72,16 +72,16 @@
     },
     "nixpkgs_2": {
       "locked": {
-        "lastModified": 1703200384,
-        "narHash": "sha256-q5j06XOsy0qHOarsYPfZYJPWbTbc8sryRxianlEPJN0=",
+        "lastModified": 1705856552,
+        "narHash": "sha256-JXfnuEf5Yd6bhMs/uvM67/joxYKoysyE3M2k6T3eWbg=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "0b3d618173114c64ab666f557504d6982665d328",
+        "rev": "612f97239e2cc474c13c9dafa0df378058c5ad8d",
         "type": "github"
       },
       "original": {
         "owner": "NixOS",
-        "ref": "nixos-23.11",
+        "ref": "nixos-unstable",
         "repo": "nixpkgs",
         "type": "github"
       }
diff --git a/flake.nix b/flake.nix
index b7dc2957..501f5e20 100644
--- a/flake.nix
+++ b/flake.nix
@@ -1,9 +1,9 @@
-{
-  description = "oliverdavies.uk-sculpin";
+# Do not edit this file. It is automatically generated by https://www.oliverdavies.uk/build-configs.
 
+{
   inputs = {
     devshell.url = "github:numtide/devshell";
-    nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
+    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
   };
 
   outputs = inputs@{ flake-parts, ... }:
@@ -15,8 +15,8 @@
       perSystem = { config, self', inputs', pkgs, system, ... }: {
         devshells.default = {
           packages = with pkgs; [
-            "nodejs"
             "nodePackages.pnpm"
+            "nodejs"
             "php81"
             "php81Packages.composer"
           ];
diff --git a/run b/run
index e173beca..dfe82d36 100755
--- a/run
+++ b/run
@@ -1,56 +1,12 @@
 #!/usr/bin/env bash
 
+set -o errexit
 set -o nounset
 set -o pipefail
 
-function clean {
-  rm -fr output_*/ source/build/
-}
+PATH="${PATH}:./vendor/bin"
 
-# Create a new daily email.
-function create-daily {
-  local date="${1}"
-  local title="${2}"
-
-  if [ "${date}" == "next" ]; then
-    next_date=$(ls -1 source/_daily_emails | tail -n 1 | tr -d '.md' | xargs -I {} date +%Y-%m-%d -d '{} +1 day')
-  else
-    next_date="${date}"
-  fi
-
-  filepath="source/_daily_emails/${next_date}.md"
-
-  shift 1
-
-  # Generate the title and slug.
-  title="${*}"
-  slug=$(echo "${title}" | \
-    tr '[:upper:]' '[:lower:]' | \
-    sed 's/[^a-z0-9]/-/g' | \
-    sed 's/\-\-+/-/g' | \
-    sed 's/^\-//;s/\-$//')
-
-  # Create the file.
-  cp -f --no-clobber stub.md "${filepath}"
-
-  date=$(date -d "${next_date}" +%Y-%m-%d)
-  day=$(date -d "${next_date}" +%d)
-  month=$(date -d "${next_date}" +%m)
-  year=$(date -d "${next_date}" +%Y)
-
-  # Replace the placeholders.
-  sed -i "s/{{ date }}/${date}/" "${filepath}"
-  sed -i "s/{{ title }}/${title}/" "${filepath}"
-  sed -i "s#{{ permalink }}#archive/${year}/${month}/${day}/${slug}#" "${filepath}"
-
-  # Create a commit with the appropriate date in the message
-  git add "${filepath}"
-  git commit --quiet -m "Add daily email for ${date}
-
-${title}"
-
-  echo "${filepath}"
-}
+# Generate the site.
 function generate {
   local args=()
 
@@ -71,35 +27,15 @@ function help {
   printf "\nExtended help:\n  Each task has comments for general usage\n"
 }
 
-function npm:build:css {
-  local args=()
-
-  if [[ "${NODE_ENV:-}" == "production" ]]; then
-    args=(--minify)
-  else
-    args=(--watch)
-  fi
-
-  npx tailwindcss \
-    --config assets/tailwind.config.ts \
-    --output source/build/tailwind.css "${args[@]}"
+# Start the project.
+function start {
+  sculpin generate --server --watch "${@}"
 }
 
-function publish {
-  export NODE_ENV=production
-  export APP_ENV=production
-
-  git stash
-
-  clean
-  npm:build:css
-  generate
-
-  rsync --archive --verbose --compress --update --delete \
-    output_prod/ ssh.oliverdavies.uk:/srv/oliverdavies.uk-sculpin
-
-  git stash pop
-}
+# Include any local tasks.
+[[ -e run.local ]] && source run.local
 
 TIMEFORMAT="Task completed in %3lR"
 time "${@:-help}"
+
+# vim: ft=bash
diff --git a/run.local b/run.local
new file mode 100755
index 00000000..bcb524b6
--- /dev/null
+++ b/run.local
@@ -0,0 +1,82 @@
+#!/usr/bin/env bash
+
+function clean {
+  rm -fr output_*/ source/build/
+}
+
+# Create a new daily email.
+function create-daily {
+  local date="${1}"
+  local title="${2}"
+
+  if [ "${date}" == "next" ]; then
+    next_date=$(ls -1 source/_daily_emails | tail -n 1 | tr -d '.md' | xargs -I {} date +%Y-%m-%d -d '{} +1 day')
+  else
+    next_date="${date}"
+  fi
+
+  filepath="source/_daily_emails/${next_date}.md"
+
+  shift 1
+
+  # Generate the title and slug.
+  title="${*}"
+  slug=$(echo "${title}" | \
+    tr '[:upper:]' '[:lower:]' | \
+    sed 's/[^a-z0-9]/-/g' | \
+    sed 's/\-\-+/-/g' | \
+    sed 's/^\-//;s/\-$//')
+
+  # Create the file.
+  cp -f --no-clobber stub.md "${filepath}"
+
+  date=$(date -d "${next_date}" +%Y-%m-%d)
+  day=$(date -d "${next_date}" +%d)
+  month=$(date -d "${next_date}" +%m)
+  year=$(date -d "${next_date}" +%Y)
+
+  # Replace the placeholders.
+  sed -i "s/{{ date }}/${date}/" "${filepath}"
+  sed -i "s/{{ title }}/${title}/" "${filepath}"
+  sed -i "s#{{ permalink }}#archive/${year}/${month}/${day}/${slug}#" "${filepath}"
+
+  # Create a commit with the appropriate date in the message
+  git add "${filepath}"
+  git commit --quiet -m "Add daily email for ${date}
+
+${title}"
+
+  echo "${filepath}"
+}
+
+function npm:build:css {
+  local args=()
+
+  if [[ "${NODE_ENV:-}" == "production" ]]; then
+    args=(--minify)
+  else
+    args=(--watch)
+  fi
+
+  npx tailwindcss \
+    --config assets/tailwind.config.ts \
+    --output source/build/tailwind.css "${args[@]}"
+}
+
+function publish {
+  export NODE_ENV=production
+  export APP_ENV=production
+
+  git stash
+
+  clean
+  npm:build:css
+  generate
+
+  rsync --archive --verbose --compress --update --delete \
+    output_prod/ ssh.oliverdavies.uk:/srv/oliverdavies.uk-sculpin
+
+  git stash pop
+}
+
+# vim: ft=bash