check / check (push) Successful in 54s
Remove the template's HTTP service, database and fx wiring. Add exact arithmetic on go/parser and go/constant, a client that runs simplex-chat as a child process and drives its WebSocket API, and the bot, which keeps an auto-accepting address and replies to each message. The image adds the checksum-pinned simplex-chat v7.0.2 on Ubuntu 22.04. Model: opus-5-5
201 lines
9.1 KiB
Markdown
201 lines
9.1 KiB
Markdown
# simplexcalc
|
|
|
|
simplexcalc is a Go chat bot by [@sneak](https://sneak.berlin) for the
|
|
SimpleX Chat network: it accepts every contact request and answers
|
|
arithmetic such as `2 + 2` with the result.
|
|
|
|
Send it `2 + 2` and it replies `4`; send `5 * 5/2` and it replies
|
|
`12.5`. It understands decimal numbers, `+ - * /`, unary minus and
|
|
parentheses, and computes exactly, so `0.1 + 0.2` is `0.3`. Anything
|
|
else gets a short explanation instead of a result.
|
|
|
|
## Getting Started
|
|
|
|
Build the image and run the bot, with its SimpleX profile on a named
|
|
volume:
|
|
|
|
```sh
|
|
git clone git@git.eeqj.de:clawbot/simplexcalc.git
|
|
cd simplexcalc
|
|
make docker
|
|
docker run -d --name simplexcalc --restart unless-stopped \
|
|
-v simplexcalc-data:/var/lib/simplexcalc simplexcalc
|
|
docker logs simplexcalc 2>&1 | grep '"msg":"ready"'
|
|
```
|
|
|
|
The `ready` log line carries the bot's contact address: `address` is the
|
|
short link to share, and `full_address` is the same address in the long
|
|
form that older SimpleX clients need. Open the link in any SimpleX Chat
|
|
app, or paste it into its "connect via link" screen; the bot accepts at
|
|
once, greets you, and answers every message you send it.
|
|
|
|
The address is stored on the `simplexcalc-data` volume. It survives
|
|
restarts, image upgrades and recreating the container, and is lost only
|
|
with the volume.
|
|
|
|
### Messaging it from a terminal
|
|
|
|
The image carries the SimpleX Chat command-line client, so a throwaway
|
|
second client can talk to the bot without installing anything. Replace
|
|
`ADDRESS` with the bot's address:
|
|
|
|
```sh
|
|
docker run --rm -v simplexcalc-tester:/var/lib/simplexcalc simplexcalc \
|
|
simplex-chat -d /var/lib/simplexcalc/tester --user-display-name tester \
|
|
-e "/c ADDRESS" -t 30 --execute-log all
|
|
docker run --rm -v simplexcalc-tester:/var/lib/simplexcalc simplexcalc \
|
|
simplex-chat -d /var/lib/simplexcalc/tester \
|
|
-e "@calc 5 * 5/2" -t 20 --execute-log messages
|
|
docker volume rm simplexcalc-tester
|
|
```
|
|
|
|
The first command connects and prints the bot's greeting; the second
|
|
sends `5 * 5/2` and prints the reply, `12.5`.
|
|
|
|
## Configuration
|
|
|
|
All configuration is environment variables, read once at startup. A
|
|
`.env` file in the working directory is loaded automatically for
|
|
development.
|
|
|
|
**A variable that is set to something unparseable aborts startup.** It
|
|
is never quietly replaced by the default. Defaults apply only to
|
|
variables that are absent.
|
|
|
|
- `DATA_DIR` — where the SimpleX database lives: the bot's profile, its
|
|
keys, its address and its contacts. Default `./data`; the image sets
|
|
`/var/lib/simplexcalc`.
|
|
- `DEBUG` — `true` or `false`, default `false`. `true` logs every event
|
|
the chat client sends.
|
|
|
|
## Entrypoints
|
|
|
|
This repo adheres to the
|
|
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all)
|
|
standard. The `script/` entrypoints, each with a thin `make` shim:
|
|
|
|
- `script/bootstrap` (`make bootstrap`) — install build dependencies
|
|
(git, make, go) idempotently. Linting additionally needs docker;
|
|
markdown formatting needs docker or a local `prettier`.
|
|
- `script/setup` (`make setup`) — `bootstrap` plus the git pre-commit
|
|
hook
|
|
- `script/test` (`make test`) — `go test` with the race detector and
|
|
coverage, `-count=1`, 90 s timeout; quiet on success, verbose rerun on
|
|
failure
|
|
- `script/lint` (`make lint`) — `golangci-lint` via docker only, against
|
|
the digest-pinned image, then `script/assert-step-ran` and
|
|
`script/assert-context-complete` over the build log. Nothing is
|
|
installed locally and nothing runs on the host.
|
|
- `script/fmt` (`make fmt`) — format Go with `gofmt` and everything else
|
|
with `prettier` (writes)
|
|
- `script/fmt-check` (`make fmt-check`) — the same scope, read-only
|
|
- `script/check` (`make check`) — `test` + `lint` + `fmt-check`. What
|
|
the pre-commit hook runs. Never modifies files.
|
|
- `script/docker` (`make docker`) — build the image, tagged with
|
|
`script/projectname`
|
|
- `script/cibuild` (`make cibuild`) — the CI gate:
|
|
`docker build --progress=plain --no-cache-filter=lint --no-cache-filter=builder .`
|
|
followed by assertions that the lint step and the test step really
|
|
ran, and that both stages really received the whole repository. The
|
|
Gitea workflow runs this on every push.
|
|
- `script/precommit` — the hook body: `go mod tidy` must not change
|
|
`go.mod`/`go.sum`, then `check`
|
|
- `script/install-precommit` (`make hooks`) — installs the hook
|
|
- `script/projectname` — prints the project name; other scripts call it
|
|
so they can stay identical across repos
|
|
- `script/prettier`, `script/assert-step-ran`,
|
|
`script/assert-context-complete`, `script/repo-source-manifest` —
|
|
helpers, not entrypoints
|
|
|
|
`make build`, `make run`, `make dev`, `make deps` and `make clean` are
|
|
the ordinary conveniences. `make run` and `make dev` run the bot on the
|
|
host, which needs the `simplex-chat` command-line client on `PATH`.
|
|
|
|
### Why the assertions exist
|
|
|
|
A green `docker build` is not evidence that the checks ran. On an
|
|
unchanged tree every layer comes from cache and the build exits 0 having
|
|
executed nothing; BuildKit silently ignores a `--no-cache-filter` naming
|
|
a stage that no longer exists; and a `.dockerignore` entry can remove a
|
|
package from the build context, after which the linter genuinely runs,
|
|
genuinely examines what it was handed, and genuinely reports `0 issues.`
|
|
over a repository with a violation in it.
|
|
|
|
So the build is not trusted. `script/assert-step-ran` requires the log
|
|
to show the step executing and the tool's own success line coming out of
|
|
it. `script/assert-context-complete` requires the file inventory each
|
|
stage emitted to match the git index — an expectation `.dockerignore`
|
|
cannot reach. Read the comments in those two scripts before changing
|
|
either; they document what they still cannot see.
|
|
|
|
## Rationale
|
|
|
|
SimpleX Chat is an end-to-end encrypted messenger with no user
|
|
identifiers: people connect through addresses they choose to share.
|
|
simplexcalc is a calculator anyone can reach that way, and a small,
|
|
complete example of a SimpleX bot in Go: profile and address set-up,
|
|
automatic acceptance, and replies, with the chat client in the same
|
|
container.
|
|
|
|
## Design
|
|
|
|
- **One process tree, one container.** `simplexcalc run` starts the
|
|
SimpleX Chat command-line client, `simplex-chat`, as a child process,
|
|
with its database under `$DATA_DIR/simplex` and its WebSocket API on
|
|
`127.0.0.1:5225`. The API has no authentication, which is why it is
|
|
never exposed outside the container.
|
|
- **The protocol** (`internal/simplex`) is JSON over that WebSocket: a
|
|
command carries a correlation id, its response carries the same id,
|
|
and anything without one is an event. Only the fields the bot reads
|
|
are decoded, so records that grow new fields in a later client release
|
|
still decode.
|
|
- **Set-up on every start** (`internal/bot`): read the bot's profile,
|
|
create its long-term address if it has none, and set the address to
|
|
accept every contact request and to greet each new contact. The first
|
|
start creates the profile itself, a bot profile named `calc`. The
|
|
address is logged in the `ready` line.
|
|
- **Replies**: for each text message a contact sends in a direct chat,
|
|
the bot sends back the result, as a reply quoting the message. Group
|
|
messages, files and the bot's own messages are ignored.
|
|
- **Arithmetic** (`internal/calc`): the text is parsed as a Go
|
|
expression with `go/parser`, and only numbers, `+ - * /`, unary signs
|
|
and parentheses are evaluated; anything else in the syntax tree is
|
|
refused. `go/constant` computes with exact rationals. Numbers are read
|
|
as decimal, so `010` is ten. Input over 256 bytes is refused, so a
|
|
message cannot make the bot do unbounded work. Whole numbers below
|
|
10<sup>21</sup> are written exactly; other results in the shortest
|
|
form that reads back as the same double, in exponent notation from
|
|
10<sup>21</sup> up and below 10<sup>-6</sup>. A result beyond the
|
|
range of a double is refused as too large.
|
|
- **Failure is an exit.** If the chat client exits or the connection to
|
|
it drops, the bot exits with an error and the container's restart
|
|
policy starts both again. `SIGTERM` stops the bot, which stops the
|
|
chat client with `SIGTERM` and kills it if it has not exited within 10
|
|
seconds.
|
|
- **The image**: a Go build stage, then an Ubuntu 22.04 runtime — the
|
|
release the chat client is built for, which already has every library
|
|
it links — with the `simplex-chat` v7.0.2 binary downloaded by an
|
|
`ADD` whose checksum BuildKit verifies. It runs as an unprivileged
|
|
user and is x86_64 only.
|
|
|
|
## Operating it
|
|
|
|
**Backup.** Everything durable is the SimpleX database on the volume:
|
|
`simplex_chat.db` and `simplex_agent.db`. Stop the container before
|
|
copying them, since a copy taken from under the running client can be
|
|
inconsistent. The database holds the bot's keys, so a copy lets its
|
|
holder answer as the bot; keep it as private as the running instance.
|
|
|
|
**Upgrade.** Rebuild the image and recreate the container with the same
|
|
volume. The chat client migrates its database on start. A newer
|
|
`simplex-chat` release is a change to the URL and the checksum on the
|
|
`ADD` line in the `Dockerfile`.
|
|
|
|
## TODO
|
|
|
|
See `docs/TODO.md` and the repository's issue tracker.
|
|
|
|
## Author
|
|
|
|
[@sneak](https://sneak.berlin)
|