hammer-editor/.github
Adam Brown b1c06e791b
Add Docker support for self-hosting the sync server (#771)
* Add Docker support for self-hosting the sync server

Provide an official Docker image for self-hosting the Hammer sync server,
alongside the existing Java executable distribution.

- docker/Dockerfile: slim, non-root runtime image on a glibc base
  (eclipse-temurin:21-jre-jammy). Runs unprivileged because the embedded
  PostgreSQL binaries refuse to run as root, and stays on glibc because those
  binaries are not musl-compatible. Pins user.home to /data so a single volume
  holds the database, caches, keyring, and config. The image packages the
  pre-built application distribution rather than compiling from source, since
  :server depends on :base and a source build would need the Android SDK.
- docker/docker-compose.yml, config.example.toml, README.md: turnkey
  self-hosting with a data volume and optional host-managed config.
- .github/workflows/publish-docker.yml: builds the distribution and publishes a
  multi-arch (amd64/arm64) image to GHCR on release. One build serves both
  arches because the distribution is pure JVM bytecode with the embedded
  PostgreSQL binaries for every OS/arch bundled inside the jars.
- .dockerignore: trims the build context to just the built distribution.
- docs/HOW-TO-RUN-A-SERVER.md: document the Docker path and drop the stale
  "Eventually we'll add Docker images" note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014KJos31QSeA2YHkCU44nNZ

* Docker: pre-create data dir so config bind mount works unprivileged

Bind-mounting a config file at /data/hammer_data/config.toml would make Docker
create the parent dir as root when it doesn't already exist, leaving the
non-root server unable to write pgdata. Pre-create and chown /data/hammer_data
in the image so a named volume initializes with it hammer-owned, and clarify the
config provisioning paths in the Docker README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014KJos31QSeA2YHkCU44nNZ

* Docker: configurable host port and a BOM caveat for config.toml

Both found while testing the image end to end on Windows.

Compose merges `ports` lists by appending, so the hardcoded 8080 mapping could
not be overridden from an override file. Drive it from HAMMER_HTTP_PORT instead,
which is the usual escape hatch when the host port is already taken.

A config.toml saved as UTF-8 with a BOM fails with an UnexpectedTokenException
pointing at line 1, which reads like a syntax error in the file. Notepad and
PowerShell's Out-File -Encoding utf8 both write one, so call it out.

* Docker: note the one-time GHCR package visibility step

The package is created automatically on first publish, but may land private,
which would break the anonymous docker pull the docs point self-hosters at.
Left as a manual step because making a package public cannot be undone.

* Docker: note why the packages:write permission block is required

The repo default workflow token is read-only, so the explicit block is what
makes the GHCR push work rather than 403.

* Docker: document external PostgreSQL, add init for orphan reaping

Remote storage already worked (the DI picks RemotePostgresDatabase and the
schema initializer runs on first connect), but nothing in the Docker setup
showed how to use it. Add a commented-out postgres service with the matching
depends_on, and a README section covering it.

The service mounts its volume at /var/lib/postgresql: postgres 18+ images abort
startup when the mount is at the older /var/lib/postgresql/data path.

Call out that storage.remote.useSsl defaults to true, which fails against a
plain postgres container that serves no TLS.

init: true reaps orphans from the embedded PostgreSQL process tree. Shutdown was
already graceful without it - the start script execs, so the JVM is PID 1 and
runs its shutdown hooks on SIGTERM - but the JVM does not reap orphans.

* Docker: split out a dedicated hosting doc and fix review findings

Move the Docker hosting guide to docs/HOW-TO-RUN-A-SERVER-DOCKER.md and reduce
the inline section in the main guide, plus docker/README.md, to pointers so the
three cannot drift apart.

Fixes:
- Pin user.home through SERVER_OPTS rather than JAVA_OPTS. The start script
  appends both, so an operator setting JAVA_OPTS for heap was silently moving the
  data directory to /home/hammer, stranding the volume and starting an empty
  database.
- Publish the plain HTTP port on 127.0.0.1 by default, overridable with
  HAMMER_HTTP_BIND. Published ports bypass host firewall rules, so the previous
  0.0.0.0 default could put cleartext credentials on the internet.
- Warn that bindHosts must not be set under Docker; it binds the container
  loopback, leaving the server unreachable but still reporting healthy.
- Gate the release trigger on the +server tag convention so client-store-only
  releases no longer republish and move latest.
- Add a ref input so a dispatch that names a version builds that ref.
- Correct the claim that cert paths resolve relative to the data directory, and
  document that renewals need a container restart.
- generate-keyring example now writes to the volume with --out.
- storage.remote host is postgres, matching the compose service name.

Also trims the narrating comments across the Dockerfile, compose file, and
workflow.

* Docker: document running the admin CLI subcommands in a container

The embedded PostgreSQL holds an exclusive lock on pgdata, so subcommands that
read the database (prune-key --role content, --converge-dry-run) cannot run
while the server container is up. It fails safely rather than corrupting
anything, but the operator has to stop the server first, and nothing said so.

Adds a table of which commands need the database, the stop/run/start sequence,
and a key-rotation walkthrough noting that a rotated keyring only takes effect
on restart. The lock is embedded-specific; remote storage has no such
constraint.

* Docs: drop em dashes from the Docker hosting guide

* Docs: drop em dashes from the server hosting guide

* Remove PageSpeed PDFs accidentally added to this branch

These were untracked working-tree files swept in by a `git add -A`; they are
unrelated to the Docker work and stay on disk.

* Pin the docker/* actions to commit SHAs

Matches how every other third-party action in this repo is referenced, and
clears the supply-chain findings Codacy raised on the PR. Kept within the major
versions the workflow was written against rather than moving to the newer
majors, since the workflow has not run yet.

* Clarify why +server releases publish the Docker image

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-25 09:53:04 -07:00
..
actions/setup-jbr Hopefully fixing JBR resolution auth 2026-06-01 23:00:11 -07:00
scripts Fix Mac App Store publish hanging forever on build processing (#769) 2026-07-21 15:33:17 -07:00
workflows Add Docker support for self-hosting the sync server (#771) 2026-07-25 09:53:04 -07:00