hadley.

Comments

A site takes comments when its config.lima says so; visitors write them in the theme’s comment form, the comments service (hadley-forms, services/forms/) keeps them, and the site’s owner moderates them on their own machine with hadley comments, which writes the approved ones into their article’s bundle. The next build shows them.

Visitors need no account and solve no puzzle: the form’s own script does a small proof of work (ALTCHA) in the browser, and nothing is loaded from another server. A comment needs a name and its text; an email address only when the visitor asks to hear of a reply, and it is never published. The service counts a visitor’s address in memory for its rate limit and does not keep it with the comment. Once moderated, the comments are deleted on the service, email addresses included; the owner’s archive keeps them on their own machine.

comments:
  enabled:    true
  url:        https://example.org          # where the comments service answers in production
  authorName: Ada                          # the name the owner's replies carry
  archive:    ../private/comments          # the moderation archive, relative to the site's directory

Locally

hadley dev starts the service with the site (docs/dev-server.md, “Comments”); hadley dev comments moderates what was written there. The local service makes its own admin token, and its archive lies in the site’s .hadley/forms/, apart from the production archive.

In production

The service runs on the site’s server, one instance per site (services/forms/systemd/hadley-forms@.service), behind the web server that passes /api/comment/* and /admin/comments to it. hadley comments reaches it at comments.url with the admin token of that instance, taken from HADLEY_COMMENTS_TOKEN or else from the file ~/.config/hadley/comments/<host>.token ($XDG_CONFIG_HOME/hadley/…), <host> being the host of site.origin. The file holds the token alone and must be readable by its owner only (mode 600); hadley refuses it otherwise.

The service can tell its owner of each new comment through ntfy (NTFY_URL, NTFY_TOKEN). With SITE_META, the path of the site’s meta.json on the server, the notification names the article by its title and opens it when tapped; meta.json is what hadley writes next to a publish build (.hadley/meta.json), and the deploy puts it beside the site’s releases.

Installing the service

Each release of hadley has the service for Linux, macOS and Windows, on amd64 and arm64, as hadley-forms-<version>-<os>-<arch>.tar.gz (on Windows .zip), with SHA256SUMS to check a download with. The archive holds the program, hadley’s licence and those of the Go code in the program, on Linux also the systemd unit. On a Linux server on amd64, in an empty directory:

(
  set -e
  version=0.4.0
  file=hadley-forms-$version-linux-amd64.tar.gz
  base=https://github.com/mkorun/hadley/releases/download/v$version
  curl -fL --remote-name-all "$base/$file" "$base/SHA256SUMS"
  grep -Fx "$(sha256sum "$file")" SHA256SUMS
  tar -xzf "$file"
  sudo install -m 755 hadley-forms /usr/local/bin/hadley-forms
  sudo install -m 644 hadley-forms@.service /etc/systemd/system/
  sudo systemctl daemon-reload
  hadley-forms -version
)

Before the first start, make the site’s data directory, for the user the service runs as, and its secrets:

site=example.org
sudo install -d -o www-data -g www-data -m 700 "/var/lib/hadley-forms/$site"
openssl rand -hex 32   # once for ADMIN_TOKEN, once for HMAC_SECRET

grep prints the line of the download in SHA256SUMS, and fails when it has none or the hash differs; with set -e, the steps after a failed one do not run. On macOS, shasum -a 256 takes the place of sha256sum. The unit starts /usr/local/bin/hadley-forms as www-data, one instance per site; its head comment says what the site’s environment file /etc/hadley-forms/<site>.env holds, and sudo systemctl enable --now hadley-forms@<site> starts it. After an update, sudo systemctl restart 'hadley-forms@*' restarts them all. A server runs it on Linux or macOS; the program for Windows is for hadley dev, which fetches it itself. To build it from the repository instead, see dev-server.md, “Comments”; scripts/build-forms.ts builds the files of a release.

Moderating

hadley comments downloads the waiting comments and shows them one at a time, on a screen of its own; when it ends, the terminal shows again what was there before, and below it how many comments were approved and rejected:

Key
a approve: the comment, and the reply drafted for it, go into the article’s comments.json and _comments.html
r reject, after a confirmation
e edit the comment’s text before approving it
n a note for the archive, not published
c draft a reply, published with the comment when it is approved
m write a notification mail as an .eml file in .hadley/mail/, for a visitor who asked for one
q stop; the next hadley comments continues where this one stopped

Every decision is kept in the archive, with the visitor’s email address; the archive is the owner’s, outside the site’s published files. Once all comments of a download are decided, hadley comments deletes them on the service. The tool speaks the language of site.default.language where it has a translation (English, German), else English.