feat: docker build for logos delivery deps

This commit is contained in:
kaichaosun 2026-07-27 13:03:35 +08:00
parent 51664cdc53
commit 2239e3ab9d
No known key found for this signature in database
GPG Key ID: 223E0F992F4F03BF
6 changed files with 259 additions and 30 deletions

2
Cargo.lock generated
View File

@ -227,6 +227,7 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chat-proto"
version = "0.1.0"
source = "git+https://github.com/logos-messaging/chat_proto?rev=e05c21229a18bab655c9aec06409189447d24a6b#e05c21229a18bab655c9aec06409189447d24a6b"
dependencies = [
"prost",
]
@ -1013,6 +1014,7 @@ checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
[[package]]
name = "logos-delivery"
version = "0.1.0"
source = "git+https://github.com/logos-messaging/libchat?rev=5c55c2ee76bebd1dbd5eb4bfa95d9e71acd0fa14#5c55c2ee76bebd1dbd5eb4bfa95d9e71acd0fa14"
dependencies = [
"base64",
"crossbeam-channel",

View File

@ -15,18 +15,16 @@ path = "src/main.rs"
anyhow = "1.0"
axum = "0.7"
base64 = "0.22"
# Wire schema for the delivery submissions, shared with libchat.
# TODO: switch to `{ git = "https://github.com/logos-messaging/chat_proto", rev = "..." }`
# once the store submission schemas land on chat_proto's main. Path dep only so
# this builds ahead of that merge.
chat-proto = { path = "../chat_proto/gen/rust" }
# Wire schema for the delivery submissions, shared with libchat. Pinned to a rev
# rather than a branch so the wire format can only change deliberately.
chat-proto = { git = "https://github.com/logos-messaging/chat_proto", rev = "e05c21229a18bab655c9aec06409189447d24a6b" }
clap = { version = "4", features = ["derive"] }
ed25519-dalek = "2.2.0"
hex = "0.4"
# Embedded logos-delivery node for subscription-based ingestion. Path dep on
# the sibling libchat checkout; links the native liblogosdelivery (enter the
# libchat dev shell or set LOGOS_DELIVERY_LIB_DIR to build a runnable binary).
logos-delivery = { path = "../libchat/extensions/logos-delivery-rust" }
# Embedded logos-delivery node for subscription-based ingestion. Links the
# native liblogosdelivery, which this repo's flake builds: `nix develop` exports
# LOGOS_DELIVERY_LIB_DIR, without which the binary fails at link.
logos-delivery = { git = "https://github.com/logos-messaging/libchat", rev = "5c55c2ee76bebd1dbd5eb4bfa95d9e71acd0fa14" }
prost = "0.14"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -3,24 +3,55 @@
########################################
# Build stage
########################################
FROM rust:1-bookworm AS builder
# Nix, not the `rust` image: the binary links the native liblogosdelivery, which
# only nix builds. That library comes from nixpkgs' glibc (2.40), newer than
# bookworm's, so cargo has to run against the same nixpkgs — mixing the two
# libcs in one process does not work. Everything below therefore builds inside
# the flake's dev shell.
FROM nixos/nix:2.32.0 AS builder
WORKDIR /app
# `filter-syscalls` off because the seccomp filter fails inside an emulated
# (qemu) container, which is how this image gets built on arm64 hosts.
RUN printf 'experimental-features = nix-command flakes\nfilter-syscalls = false\n' \
>> /etc/nix/nix.conf
# Build dependencies first against a stub binary so the (slow) dependency
# compilation — bundled libsqlite3 and the async stack — is cached and only
# re-runs when Cargo.toml/Cargo.lock change.
WORKDIR /src
# The native library first and on its own layer: it is by far the slowest part
# of the build and only changes when the flake pins do.
COPY flake.nix flake.lock ./
RUN nix --accept-flake-config build .#logos-delivery --no-link
# Then the Rust dependency graph, against a stub binary, so the (also slow)
# dependency compile is cached and only re-runs when Cargo.toml/Cargo.lock change.
COPY Cargo.toml Cargo.lock ./
RUN mkdir src \
&& echo "fn main() {}" > src/main.rs \
&& cargo build --release --locked \
&& nix --accept-flake-config develop --command cargo build --release --locked \
&& rm -rf src target/release/deps/chat_store* target/release/chat-store
# Now build the real binary; dependency artifacts above are reused. `migrations/`
# is needed at build time too — `sqlx::migrate!()` embeds the .sql files.
# Now the real binary; dependency artifacts above are reused. `migrations/` is
# needed at build time too — `sqlx::migrate!()` embeds the .sql files.
COPY src ./src
COPY migrations ./migrations
RUN cargo build --release --locked --bin chat-store
RUN nix --accept-flake-config develop --command \
cargo build --release --locked --bin chat-store
# Collect what the binary loads at runtime. logos-delivery's build.rs stamps
# liblogosdelivery with an absolute store path as its soname, so the binary
# names it — along with its ELF interpreter and rpath — by absolute /nix/store
# path. Seeding the closure from those three is all the runtime image needs.
RUN nix --accept-flake-config develop --command sh -c '\
bin=target/release/chat-store; \
{ patchelf --print-needed "$bin"; \
patchelf --print-interpreter "$bin"; \
patchelf --print-rpath "$bin" | tr ":" "\n"; } \
| grep "^/nix/store/" \
| sed "s|\(/nix/store/[^/]*\).*|\1|" | sort -u \
| xargs nix-store --query --requisites | sort -u > /closure.txt' \
&& mkdir -p /output/nix/store \
&& cp target/release/chat-store /output/chat-store \
&& cp -a $(cat /closure.txt) /output/nix/store/
########################################
# Runtime stage
@ -31,7 +62,9 @@ RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/chat-store /usr/local/bin/chat-store
# The nix closure the binary resolves by absolute path, then the binary itself.
COPY --from=builder /output/nix/store /nix/store
COPY --from=builder /output/chat-store /usr/local/bin/chat-store
# Matches the default --bind 0.0.0.0:8080.
EXPOSE 8080

View File

@ -8,14 +8,14 @@ its own.
Standalone service that caches MLS KeyPackages keyed by **`device_id`**, so a
client can fetch a contact's keypackage without an out-of-band exchange.
Throwaway by design: scheduled to be replaced by a λLEZ-based service in v0.3,
with no libchat-core dependency (the embedded logos-delivery node comes from the
sibling libchat checkout's transport crate).
with no libchat-core dependency (the embedded logos-delivery node comes from
libchat's transport crate, pulled in as a pinned git dependency).
Submissions arrive on either of two write paths feeding the same verification +
storage pipeline:
- **HTTP POST** (`/v0/keypackage`, `/v0/account`) — synchronous and acknowledged;
- **logos-delivery subscription** — clients publish the same JSON bodies on the
- **logos-delivery subscription** — clients publish protobuf submissions on the
store's content topics and the server picks them up from the network (see
[Delivery ingestion](#delivery-ingestion)).
@ -56,11 +56,17 @@ exactly one way — no delimiter, even though `key_package` is arbitrary bytes.
## Building & running
Building a runnable binary links the native `liblogosdelivery`, which this
repo's flake builds. The dev shell exports `LOGOS_DELIVERY_LIB_DIR` for you:
```bash
nix develop # or set LOGOS_DELIVERY_LIB_DIR yourself
cargo build --release
./target/release/chat-store # binds 0.0.0.0:8080, db ./chat-store.db
```
Without the library, `cargo check` and `clippy` still pass — only linking fails.
| Flag | Default | Description |
|------|---------|-------------|
| `--bind <addr>` | `0.0.0.0:8080` | HTTP bind address |
@ -74,10 +80,6 @@ cargo build --release
Logs via `RUST_LOG` (default `info`).
Building a runnable binary links the native `liblogosdelivery`; enter the
libchat dev shell (`nix develop` in the sibling checkout) or set
`LOGOS_DELIVERY_LIB_DIR` to the directory containing the library.
## Delivery ingestion
Unless `--no-delivery` is given, the server runs an embedded logos-delivery
@ -88,12 +90,17 @@ node and subscribes to two content topics:
/logos-chat/1/store-account-v0/proto account device-list submissions
```
Each received message is the same JSON body as the corresponding POST endpoint
(below) and goes through identical signature verification and storage rules.
Each received message is a protobuf `KeyPackageSubmissionV1` or
`AccountSubmissionV1` — matching the `/proto` topic suffix — carrying the same
fields as the corresponding POST body and going through identical signature
verification and storage rules. The schemas live in
[chat_proto](https://github.com/logos-messaging/chat_proto) (`protos/store.proto`),
pinned by rev in `Cargo.toml` so the wire format only changes deliberately.
Publishing is fire-and-forget on the client side: rejected submissions are only
logged by the server, which the trust model can afford because consumers verify
every bundle on retrieval anyway. libchat's `DeliveryRegistry` publishes on
these topics when its publish mode is set to delivery.
every bundle on retrieval anyway. libchat's `ContactRegistry` publishes on these
topics when constructed with `RegistryPublishMode::Delivery`.
## Docker
@ -105,6 +112,13 @@ docker build -t chat-store .
docker run --rm -p 8080:8080 -v chat-store-data:/data chat-store
```
The build runs inside nix, because the binary links `liblogosdelivery` and the
nixpkgs glibc it is built against is newer than bookworm's — so cargo has to run
against that same nixpkgs. The runtime image is still `debian:bookworm-slim`,
carrying only the nix store closure the binary resolves by absolute path. The
first build compiles the native library from source and takes a long time;
afterwards it is a cached layer that only changes with `flake.lock`.
The image runs the binary with `--bind 0.0.0.0:8080 --db /data/chat-store.db`
by default; override the `CMD` to change flags, e.g.:
@ -262,6 +276,11 @@ A non-zero exit from `chat-cli` means the server rejected the submission — e.g
the signature failed verification. `GET /v0/keypackage/{device_id}` returns `200`
for a registered device and `404` otherwise.
Add `--registry-publish delivery` to those `chat-cli` invocations to exercise
the delivery write path instead of the HTTP POST API. Publishing is then
fire-and-forget, so `chat-cli` exits `0` regardless and the bundles appear in
the database only once the server has received and accepted them.
## Benchmark
The bundled [`benchmark`](examples/benchmark.rs) performs an end-to-end load

115
flake.lock generated Normal file
View File

@ -0,0 +1,115 @@
{
"nodes": {
"logos-delivery": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"rust-overlay": "rust-overlay",
"zerokit": "zerokit"
},
"locked": {
"lastModified": 1779915920,
"narHash": "sha256-rcIgP6MVyUoNEH6xpdLrZtfd4OcvIcMUloX4IhRq5AA=",
"owner": "logos-messaging",
"repo": "logos-delivery",
"rev": "74057c66224f43b4aa27b42033d4ed52eed5c7a7",
"type": "github"
},
"original": {
"owner": "logos-messaging",
"repo": "logos-delivery",
"rev": "74057c66224f43b4aa27b42033d4ed52eed5c7a7",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1782231800,
"narHash": "sha256-AyPvq+cx3JFicSd687dhhIfUMryk9PUFmHnofBjalMg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6f5e2d2ea55618c5197ce40e346f205c35d2213e",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6f5e2d2ea55618c5197ce40e346f205c35d2213e",
"type": "github"
}
},
"root": {
"inputs": {
"logos-delivery": "logos-delivery",
"nixpkgs": "nixpkgs"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"logos-delivery",
"nixpkgs"
]
},
"locked": {
"lastModified": 1775099554,
"narHash": "sha256-3xBsGnGDLOFtnPZ1D3j2LU19wpAlYefRKTlkv648rU0=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "8d6387ed6d8e6e6672fd3ed4b61b59d44b124d99",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"rust-overlay_2": {
"inputs": {
"nixpkgs": [
"logos-delivery",
"zerokit",
"nixpkgs"
]
},
"locked": {
"lastModified": 1771211437,
"narHash": "sha256-lcNK438i4DGtyA+bPXXyVLHVmJjYpVKmpux9WASa3ro=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "c62195b3d6e1bb11e0c2fb2a494117d3b55d410f",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"zerokit": {
"inputs": {
"nixpkgs": [
"logos-delivery",
"nixpkgs"
],
"rust-overlay": "rust-overlay_2"
},
"locked": {
"owner": "vacp2p",
"repo": "zerokit",
"rev": "5e64cb8822bee65eed6cf459f95ae72b80c6ba63",
"type": "github"
},
"original": {
"owner": "vacp2p",
"repo": "zerokit",
"rev": "5e64cb8822bee65eed6cf459f95ae72b80c6ba63",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

62
flake.nix Normal file
View File

@ -0,0 +1,62 @@
{
description = "chat-store - keypackage and account registry for Logos Chat";
# Repeated from the logos-delivery flake: only the top-level flake's nixConfig
# is honoured, so an input's substituters do nothing for us.
nixConfig = {
extra-substituters = [ "https://nix-cache.status.im/" ];
extra-trusted-public-keys = [
"nix-cache.status.im-1:x/93lOfLU+duPplwMSBR+OlY4+mo+dCN7n0mr4oPwgY="
];
};
inputs = {
# Both revs are exactly what libchat's flake.lock pins, and they only work
# as a pair: logos-delivery's derivation wants `nim-2_2`, which newer
# nixos-unstable-small no longer has. Bump the two together.
nixpkgs.url = "github:NixOS/nixpkgs/6f5e2d2ea55618c5197ce40e346f205c35d2213e";
# `follows` keeps the library and the toolchain that links it on a single
# glibc — the Docker build depends on that.
logos-delivery = {
url = "github:logos-messaging/logos-delivery/74057c66224f43b4aa27b42033d4ed52eed5c7a7";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, logos-delivery }:
let
systems = [ "aarch64-darwin" "x86_64-darwin" "aarch64-linux" "x86_64-linux" ];
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f {
inherit system;
pkgs = import nixpkgs { inherit system; };
});
in
{
# The one thing this flake exists for: the native library the
# `logos-delivery` crate links. Same override as libchat's flake — no
# postgres archive driver, no Nim dlopen debug hooks, quiet logs.
packages = forAllSystems ({ system, ... }: {
logos-delivery = logos-delivery.packages.${system}.liblogosdelivery.override {
enablePostgres = false;
enableNimDebugDlOpen = false;
chroniclesLogLevel = "FATAL";
};
default = self.packages.${system}.logos-delivery;
});
devShells = forAllSystems ({ pkgs, system, ... }:
let logosDeliveryLib = self.packages.${system}.logos-delivery;
in {
default = pkgs.mkShell {
nativeBuildInputs = [ pkgs.cargo pkgs.rustc pkgs.pkg-config ]
# logos-delivery's build.rs rewrites the shipped library's soname
# with patchelf in its default (absolute-path) linking mode.
++ pkgs.lib.optionals pkgs.stdenv.isLinux [ pkgs.patchelf ];
buildInputs = [ logosDeliveryLib ];
shellHook = ''
export LOGOS_DELIVERY_LIB_DIR="${logosDeliveryLib}/lib"
'';
};
});
};
}