Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ concurrency:
jobs:
build:
if: github.event.pull_request.draft == false
uses: start9labs/shared-workflows/.github/workflows/build.yml@master
# with:
# FREE_DISK_SPACE: true
uses: Start9Labs/start-technologies/.github/workflows/build.yml@master
secrets:
DEV_KEY: ${{ secrets.DEV_KEY }}
3 changes: 1 addition & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ on:

jobs:
release:
uses: start9labs/shared-workflows/.github/workflows/release.yml@master
uses: Start9Labs/start-technologies/.github/workflows/release.yml@master
with:
# FREE_DISK_SPACE: true
RELEASE_REGISTRY: ${{ vars.RELEASE_REGISTRY }}
S3_S9PKS_BASE_URL: ${{ vars.S3_S9PKS_BASE_URL }}
secrets:
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/tagAndRelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ concurrency:

jobs:
tag:
uses: start9labs/shared-workflows/.github/workflows/tagAndRelease.yml@master
uses: Start9Labs/start-technologies/.github/workflows/tagAndRelease.yml@master
with:
REFERENCE_REGISTRY: ${{ vars.REFERENCE_REGISTRY }}
# FREE_DISK_SPACE: true
RELEASE_REGISTRY: ${{ vars.RELEASE_REGISTRY }}
S3_S9PKS_BASE_URL: ${{ vars.S3_S9PKS_BASE_URL }}
secrets:
Expand Down
11 changes: 11 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,14 @@ which provides the packaging guide and agent context one level up. If you're rea
bare clone with no workspace, the full guide is at <https://docs.start9.com/packaging>.

Work this package's `TODO.md` from top to bottom. Keep `README.md` (architecture, for developers and LLMs) and `instructions.md` (end-user docs) in sync with your changes.

## This repo

- **Package id is `simplex-websocket-bridge`.** It runs a headless SimpleX Chat client and exposes the SimpleX network over a token-authenticated WebSocket API (interface id `ws` on host `main`, port 5225). Bearer auth is enforced at the StartOS reverse proxy via `addSsl.auth`; same-box dependents and this package's own actions bypass the proxy by dialing the container bridge IP directly.
- **The `websocket` standalone health check and the `ws` interface are a dependent-facing contract** (see README's file-exchange section). Dependents reference the `websocket` id in a `kind: 'running'` requirement — treat both ids as a small API and update consumers if you rename them.
- **File exchange is a SINGLE `/simplex` mount, not one per subdir.** `utils.ts` re-mounts the `main` volume's `.simplex/media` subpath at `/simplex` (exposing `/simplex/inbound`, `/simplex/tmp`, `/simplex/outbound`) as one mount, because simplex-chat moves completed downloads temp→files with an atomic rename that `EXDEV`-fails across separate bind mounts. Don't split it.
- **Actions drive the running bot over its WebSocket control protocol**, not a shell — `bot-client.ts` opens `ws://<container-ip>:5225` and speaks newline-delimited JSON-RPC (`create-invitation`, `configure`).

## Inspecting a running install

To run a command inside the service's container (read its generated config, grep app logs), use `start-cli package attach simplex-websocket-bridge -n simplex-sub -- <cmd>`. Select the subcontainer by **name** with `-n` (the name passed to `SubContainer.of` in `main.ts` — here `simplex-sub`) or by image with `-i`. Note: `-s/--subcontainer` matches the internal **Guid**, not the name, so passing a name to `-s` fails with "no matching subcontainers".
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
ARCHES := x86_64 aarch64
# overrides to s9pk.mk must precede the include statement
include s9pk.mk
include node_modules/@start9labs/start-sdk/s9pk.mk
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ The bundled client is driven over its Websocket using the upstream [SimpleX bot/

| Property | Value |
| ------------- | -------------------------------------------------------------- |
| Image | `lundog/simplex-chat` (built from `lundog/simplex-chat-docker`) |
| Image | `lundog/simplex-websocket-bridge` (built from `lundog/simplex-websocket-bridge-docker`) |
| Architectures | x86_64, aarch64 |
| Command | Image entrypoint — supervises `simplex-chat` and `websocat` |

Expand Down Expand Up @@ -66,6 +66,8 @@ The Websocket carries only a small inline preview for image/video messages — a

**Paths:** a consumer that mounts these subpaths at the *same mountpoints* can use the paths verbatim. This is strictly required only for **outbound** — that path travels over the Websocket and is resolved inside the bridge's container, so it must be valid there. **Inbound** is looser: the Websocket API reports only a filename, which the consumer resolves against its own inbound directory, so the two sides need only share the same host directory. The neutral `/simplex` prefix (rather than `/data/...`) lets consumers mount at identical paths without colliding with their own `/data` volume.

**These paths are set explicitly, not inherited.** `main.ts` passes `SIMPLEX_INBOUND_DIR=/simplex/inbound` and `SIMPLEX_TMP_DIR=/simplex/tmp` into the container. Do not drop them and fall back to the image's defaults: those defaults have moved before (through image 6.5.4 they were `/simplex`; 6.5.5 changed them to `$HOME/.simplex/{files,tmp}`, which is *outside* this mount). A bridge writing outside `/simplex` breaks this contract silently — consumers see an empty `inbound` and no error is raised anywhere.

**Security:** consumers mount only the `media/*` subpaths — never the whole `main` volume or `.simplex/` itself, which hold the SimpleX profile database and keys. `inbound` is read-only so consumers can't alter received files; write access is limited to `outbound`.

---
Expand Down Expand Up @@ -160,7 +162,7 @@ The bundled `simplex-chat` client behaves exactly as upstream: messages and file

```yaml
package_id: simplex-websocket-bridge
image: lundog/simplex-chat
image: lundog/simplex-websocket-bridge
architectures: [x86_64, aarch64]
volumes:
main: /data
Expand Down
84 changes: 63 additions & 21 deletions UPDATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,74 @@

SimpleX Websocket Bridge runs the [SimpleX Chat](https://github.com/simplex-chat/simplex-chat)
client headless. The runtime is the standalone container image built from
[lundog/simplex-chat-docker](https://github.com/lundog/simplex-chat-docker) and
published to Docker Hub as `lundog/simplex-chat`. "Upstream" here means the
SimpleX Chat release that image bundles; this repo consumes the image via
`dockerTag` and does not build it.
[lundog/simplex-websocket-bridge-docker](https://github.com/lundog/simplex-websocket-bridge-docker)
and published to Docker Hub as `lundog/simplex-websocket-bridge`. "Upstream" here means the
SimpleX Chat release that image bundles; this repo consumes the image via `dockerTag` and does
not build it.

> [!NOTE]
> Both the image and its source repo were renamed in July 2026 (at image revision `6.5.5-2`), from
> `lundog/simplex-chat` / `lundog/simplex-chat-docker`. The GitHub repo redirects, but the old
> Docker Hub repo does **not** — it is frozen at `6.5.5-1` and gets no new builds. Don't pin it.

## The tag has two parts

The tag is `<simplex-version>[-<image-revision>]`, e.g. `6.5.5-2`, and the two halves move
independently:

- **SimpleX version** (`6.5.5`) — the SimpleX Chat release the image bundles.
- **Image revision** (`-2`) — a rebuild of the *same* SimpleX version (an entrypoint or packaging
change in the image repo). An unsuffixed tag is revision 0.

An unsuffixed tag points at the **newest** revision of that SimpleX version, so `6.5.5` and
`6.5.5-2` are currently the same image. Pin the explicit revision so the pin can't shift underneath
you.

## Determining the upstream version

- **simplex-chat** ([simplex-chat/simplex-chat](https://github.com/simplex-chat/simplex-chat)) — fetch the latest release tag:
**Do not bump from the SimpleX Chat release list alone.** A SimpleX release is only usable once the
bridge image has been rebuilt for it, and that rebuild is a separate, manual step by the image
maintainer — it lags, and sometimes never happens for a given release. Read the image's tag list,
which is the only thing that actually has to exist:

```sh
gh release view -R simplex-chat/simplex-chat --json tagName -q .tagName
```
```sh
curl -fsSL "https://hub.docker.com/v2/repositories/lundog/simplex-websocket-bridge/tags?page_size=50&ordering=last_updated" \
| jq -r '.results[].name'
```

The current pin lives in `startos/manifest/index.ts` at
`images.simplex.source.dockerTag` (the version after the `:` in
`lundog/simplex-chat:<version>`).
For context, the newest SimpleX Chat release is:

```sh
gh release view -R simplex-chat/simplex-chat --json tagName -q .tagName
```

If it has no matching image tag, there is nothing to bump here yet — the image must be built first.

The pin lives in `startos/manifest/index.ts` at `images.simplex.source.dockerTag`.

## Applying the bump

1. In [lundog/simplex-chat-docker](https://github.com/lundog/simplex-chat-docker),
bump the pinned simplex-chat version, then build and publish a matching
multi-arch (amd64 + arm64) tag to `lundog/simplex-chat`. The Dockerfile,
entrypoint supervisor, and the `--files-folder`/`--temp-folder` flags all
live there, not in this repo.
2. Bump `dockerTag` in `startos/manifest/index.ts` to
`lundog/simplex-chat:<new version>` (drop the leading `v` from the release tag).
3. Bump the package `version` and update `releaseNotes` in
`startos/versions/current.ts`. Add a migration only if the new version needs
one (see the [packaging guide on versions](https://docs.start9.com/packaging)).
1. If the image hasn't been rebuilt for the SimpleX release you want, that comes first: in
[lundog/simplex-websocket-bridge-docker](https://github.com/lundog/simplex-websocket-bridge-docker),
bump the pinned SimpleX version and publish a multi-arch (amd64 + arm64) tag. The Dockerfile,
the entrypoint supervisor, and the `--files-folder` / `--temp-folder` wiring all live there, not
in this repo.
2. Bump `dockerTag` to `lundog/simplex-websocket-bridge:<tag>` (drop the leading `v` from the
SimpleX release tag; keep the explicit `-N` revision).
3. **Re-check the image's file-exchange defaults.** The bridge's inbound and temp directories come
from `SIMPLEX_INBOUND_DIR` / `SIMPLEX_TMP_DIR`, which this package sets **explicitly** in
`startos/main.ts` (to `/simplex/inbound` and `/simplex/tmp`, inside the shared `/simplex` mount
— see `startos/utils.ts` and the README's file-exchange contract).

We set them precisely because the image's defaults have moved before and will not announce it:
through 6.5.4 the image defaulted to `/simplex`, and 6.5.5 silently changed it to
`$HOME/.simplex/{files,tmp}` — *outside* our mount. A bump that relied on the defaults would send
every received file somewhere no dependent service can see, breaking the file-exchange contract
with no error.

So on each bump, confirm the image still honors both variables, and that inbound and temp stay
co-located: simplex-chat completes a download with an atomic `rename(2)` from temp into inbound,
which fails with `EXDEV` if they are separate mounts.
4. Bump the package `version` and update `releaseNotes` in `startos/versions/current.ts`. Add a
migration only if the new version needs one (see the
[packaging guide on versions](https://docs.start9.com/packaging)).
Loading