brewlog/README.md
Niels Göttsch 423ddc7870
Some checks failed
Build & Publish / Check (push) Successful in 2m15s
Build & Publish / Build & Publish (push) Failing after 9s
ci: build image daemonless with Kaniko (no Docker)
Replace the docker buildx / build-push-action build job with Kaniko running as
the job container: it builds the Dockerfile from the git context and pushes to
the forge registry, with no Docker daemon, no buildx, and no privileged DinD.

Kaniko cannot handle BuildKit `RUN --mount=type=cache`, so drop the two cache
mounts on the cargo build layer (plain `cargo build` now). Document the pipeline
and this gotcha (plus the node20-only runner and full-github-URL action rules) in
the README so it isn't reintroduced.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 17:17:09 +02:00

198 lines
8.5 KiB
Markdown

![cover image](./static/og-image.png)
**B{rew}log** is a self-hosted specialty coffee logging platform optimised for filter brewing
enthusiasts. B{rew}log can be used for tracking roasters, roasts, brews, cafes and brewing gear.
B{rew}log features an LLM-powered "Bag Scanning" feature, which enables it to automatically fill
roaster and coffee information using a photo of a bag. It also supports "check-ins" to log coffee
enjoyed in a cafe.
B{rew}log ships as a single Rust binary that serves a web UI, a REST API, and a CLI client. The
application uses SQLite as a backend, and will automatically create and migrate the database on
start-up.
> [!NOTE]
> This application was built almost entirely with Claude Code. I used this project as an
> excuse to explore the current state of the art in agentic coding tools. I've reviewed
> much of the code, and I'm largely responsible for the rules and the layout of the repository
> but nonetheless the vast majority of the code was written by Claude and Opus 4.6.
## Quick Start (Demo)
Before you start, you'll need to sign up for [Openrouter](https://openrouter.ai) and
[Foursquare Places](https://foursquare.com/products/places/) and get API keys for both.
Then create a `docker.env` file:
```env
# You'll need an API key from OpenRouter
BREWLOG_OPENROUTER_API_KEY=sk-or-...
# I've had good results with Gemini models, but you can try 'openrouter/free' to experiment
BREWLOG_OPENROUTER_MODEL=google/gemini-3-flash-preview
# FourSquare Places API key for location searching
BREWLOG_FOURSQUARE_API_KEY=fsq3...
```
You can see the full list of configuration options [below](#configuration). Once your `.env` file
is complete, start the container using the environment file
```bash
# Create a data directory to store the database
mkdir data
# Run the container
docker run \
--rm \
-p 3000 \
--env-file docker.env \
-v $PWD/data:/data \
ghcr.io/jnsgruk/brewlog:latest
```
On first start with an empty database the server prints a one-time registration URL:
```
No users found. Register the first user at:
http://localhost:3000/register/abc123...
This link expires in 1 hour.
```
Open that URL, choose a display name, and register a passkey. This creates an account and
signs in automatically.
To onboard additional users later, generate a fresh invite link from the **Admin** page
("Invite" → "New Invite") or from the CLI (`brewlog invite create`). Each link is valid for
7 days and can be used once.
### Install from Git
To build and install from source, you'll need a working Rust toolchain:
```bash
cargo install --locked --git https://github.com/jnsgruk/brewlog.git
```
Then create a `.env` file containing at least your OpenRouter and Foursquare API keys, and start
the server:
```bash
brewlog serve
```
### CLI Authentication
To use the CLI or API for write operations, create a token via browser hand-off:
```bash
brewlog token create --name "my-cli-token"
# Browser opens → authenticate with a passkey → token printed once
export BREWLOG_URL="http://localhost:3000"
export BREWLOG_TOKEN="<token from above>"
# Create data from the CLI
brewlog roaster add --name "Radical Roasters" --country "United Kingdom"
```
To invite another person, generate a single-use registration link (valid for 7 days):
```bash
brewlog invite create
# Prints a https://.../register/<token> link to share with the new user
```
Run `brewlog --help` for the full command reference.
## Configuration
All settings are read from environment variables or CLI flags. A `.env` file in the working
directory is loaded automatically via [dotenvy](https://crates.io/crates/dotenvy).
### Server (`brewlog serve`)
| Variable | Purpose | Default |
| -------------------------- | ---------------------------------------------------------------------- | ----------------------- |
| `BREWLOG_RP_ID` | WebAuthn Relying Party ID (server domain) | `localhost` |
| `BREWLOG_RP_ORIGIN` | WebAuthn Relying Party origin (full URL) | `http://localhost:3000` |
| `BREWLOG_DATABASE_URL` | Database connection string | `sqlite://brewlog.db` |
| `BREWLOG_BIND_ADDRESS` | Server bind address | `127.0.0.1:3000` |
| `BREWLOG_INSECURE_COOKIES` | Disable the `Secure` cookie flag (auto-enabled for localhost defaults) | `false` |
| `RUST_LOG` | Log level filter | `info` |
| `RUST_LOG_FORMAT` | Set to `json` for structured log output | — |
### CLI Client
| Variable | Purpose | Default |
| --------------- | ------------------------------------- | ----------------------- |
| `BREWLOG_URL` | Server URL | `http://localhost:3000` |
| `BREWLOG_TOKEN` | API bearer token for write operations | — |
### Integrations
| Variable | Purpose | Default |
| ---------------------------- | --------------------------------------------------------------------------- | ----------------- |
| `BREWLOG_OPENROUTER_API_KEY` | [OpenRouter](https://openrouter.ai/) API key for AI extraction | **required** |
| `BREWLOG_OPENROUTER_MODEL` | LLM model for AI extraction | `openrouter/free` |
| `BREWLOG_FOURSQUARE_API_KEY` | [Foursquare](https://foursquare.com/) Places API key for nearby cafe search | **required** |
## Development Setup
Install [mise](https://mise.jdx.dev/) then set up the development environment:
```bash
sudo apt update
sudo apt install -y clang mold pkg-config libssl-dev \
libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 \
libxcomposite1 libxdamage1 libxrandr2 libgbm1 libpango-1.0-0 \
libcairo2 libasound2t64 libnss3 libxshmfence1
mise trust
mise install # Install all dev tools
mise run install-e2e # Install Chrome for Testing + ChromeDriver
prek install # Install git hooks
```
## Contributing
```bash
prek run -av # Run all lints, tests, formatters
mise run fmt # Format all files
mise run check # Full CI validation (fmt + lint + test)
mise run test # Run all tests
cargo build # Build
```
See [CLAUDE.md](CLAUDE.md) for architecture, code patterns, and development conventions.
## CI / build pipeline (this fork)
This fork lives on the **zo** Forgejo forge
(`git.ziemlichoptimal.de/uberbau/brewlog`, default branch **`dev`**) and is
deployed to the **moby** homelab via Flux GitOps. `.github/workflows/deploy.yml`
runs on every push to `dev`: it lints/tests (`check`), then builds and publishes
the container image to the forge's own registry as
`git.ziemlichoptimal.de/uberbau/brewlog:{dev,<sha>}`. The moby deployment pins
that image by digest and bumps it per release.
The build is **daemonless — it does not use Docker**. It runs
[Kaniko](https://github.com/GoogleContainerTools/kaniko) as the job container,
building the `Dockerfile` (context pulled straight from git) and pushing to the
registry. No `docker`, no buildx, no privileged Docker-in-Docker.
### Gotchas (learned the hard way)
- **Kaniko does not support BuildKit `RUN --mount=type=cache`.** The build fails
on it. Keep every `RUN` in the `Dockerfile` plain — no BuildKit cache mounts.
(This is why the Rust build layer is a plain `cargo build`; we have been bitten
by this before, so do not "re-add caching" to the Dockerfile.)
- **The forge's Actions runner is node20-only.** Pin JS actions to their node20
generation (`actions/checkout@v4`, `actions/cache@v4`, and if you reintroduce
docker actions, `@v3`/`@v6`) — the newer `@v5/@v6/@v7` releases declare
`runs.using: node24`, which the runner rejects (`must be one of [… node20 …]`).
- **Reference actions by full `github.com` URL**
(`uses: https://github.com/owner/repo@ref`). The runner's default action
mirror (`code.forgejo.org`) is incomplete/flaky and aborts clones mid-run.
- The `check` job installs only Rust via mise (`install_args: rust`); the full
`mise.toml` toolchain pulls tools from the GitHub API, which 401s on the runner.
## License
[Apache License 2.0](LICENSE)