diff --git a/README.md b/README.md index 59e21d2..8e6f97e 100644 --- a/README.md +++ b/README.md @@ -65,11 +65,11 @@ that was published: ```console $ sendan verify https://demo.sendan.app instance https://demo.sendan.app - claims v0.3.0, commit 7d7904e, unmodified - manifest v0.3.0 + claims v0.3.3, commit a5fbc01, unmodified + manifest v0.3.3 signed by this build's release keys, classical and post-quantum - ✓ 31 of 31 assets match the published client + ✓ 33 of 33 assets match the published client ``` That compares what the instance serves against a manifest taken from the @@ -77,6 +77,60 @@ release rather than from the instance, so the answer does not depend on the instance being honest. Run it against any instance somebody sends you a link from, including this one. +## Run it + +One container, no runtime dependency. The image defaults to SQLite and +filesystem storage, so this is a working instance on +: + +```sh +docker run -d --name sendan -p 8080:8080 \ + -v sendan:/var/lib/sendan \ + ghcr.io/serraniel/sendan:0.3.3 +``` + +Image tags follow the releases and carry **no `v`**, so pin `:0.3.3` rather +than `:latest` for a deployment that should not move on its own. + +That is enough to try it locally. It is deliberately not enough to deploy: +download links are built from `SENDAN_BASE_URL`, which defaults to +`http://localhost:8080`, and the browser client needs TLS for more than +confidentiality — streaming an upload requires HTTP/2, which browsers only +offer over a secure origin. + +So for anything reachable by other people, [`compose.yaml`](compose.yaml) runs +Sendan behind a reverse proxy that obtains its own certificate: + +```sh +SENDAN_DOMAIN=files.example.org docker compose up -d +``` + +The domain must already resolve to the host and ports 80 and 443 must reach it, +because the proxy proves control of the name to get a certificate. That is the +whole deployment. + +| Going further | | +|---|---| +| PostgreSQL, S3-compatible storage, backups, the at-rest master key | [`docs/deployment.md`](docs/deployment.md) | +| Every variable and its default | [`docs/configuration.md`](docs/configuration.md) | + +### The command line client + +A single static binary, for Linux, macOS and Windows on both architectures. +Using it means never executing an instance's code, which is the one thing a +browser cannot avoid: + +```sh +curl -LO https://github.com/Serraniel/sendan/releases/latest/download/sendan-linux-amd64 +curl -LO https://github.com/Serraniel/sendan/releases/latest/download/SHA256SUMS + +sha256sum -c SHA256SUMS --ignore-missing +chmod +x sendan-linux-amd64 && sudo mv sendan-linux-amd64 /usr/local/bin/sendan +``` + +[`docs/cli.md`](docs/cli.md) has the other five platforms, every command and +option, and how to check the release signatures by hand. + ## Features - **End-to-end encryption in every code path.** There is no configuration in @@ -97,10 +151,11 @@ from, including this one. must be enabled explicitly. - **Single-container deployment.** One static binary serving an embedded web client, on an image with no distribution, no package manager and no shell. - *(Builds and runs; not yet published — see the status above.)* + *(Published at `ghcr.io/serraniel/sendan` for `linux/amd64` and + `linux/arm64`, signed, with provenance and an SBOM.)* - **Cross-platform command line client**, sharing one cryptographic - implementation with the server. *(Sends and receives; no binary published - yet — see the status above.)* + implementation with the server. *(Signed static binaries for six + platforms; [`docs/cli.md`](docs/cli.md) has the install steps.)* - **Light and dark themes**, following the system by default with a control to override it, on one layout that holds from a small phone to a desktop display. No web font is loaded and no request leaves the instance: the whole diff --git a/SECURITY.md b/SECURITY.md index a4fac29..1adc8a6 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -177,13 +177,6 @@ assume. Each release publishes a binary for every supported platform and a [github.com/Serraniel/sendan/releases](https://github.com/Serraniel/sendan/releases). [`docs/cli.md`](docs/cli.md) has the per-platform download and install steps. -> [!NOTE] -> **Nothing published carries these signatures yet.** `v0.1.0` is tagged, but -> the workflow that builds and signs its artefacts was never started, and the -> release keys do not exist. This describes what the pipeline publishes once -> both are in place. Until then, build from source — which is the stronger -> check anyway, and is the second procedure below. - **Check what you downloaded:** ```sh @@ -196,9 +189,11 @@ the release was built from the source in this repository — for that, build it yourself and compare: ```sh -git checkout v0.5.0 # the tag the release names +VERSION=v0.3.3 # the tag the release names + +git checkout "$VERSION" -SENDAN_VERSION=v0.5.0 \ +SENDAN_VERSION="$VERSION" \ SENDAN_COMMIT= \ ./scripts/release-build.sh @@ -255,8 +250,8 @@ sendan verify https://files.example.org ``` instance https://files.example.org - claims v0.5.0, commit 1a2b3c4, unmodified - manifest v0.5.0 + claims v0.3.3, commit 1a2b3c4, unmodified + manifest v0.3.3 signed by this build's release key ✓ 41 of 41 assets match the published client @@ -315,7 +310,7 @@ The first is [minisign](https://jedisct1.github.io/minisign/) format, so the command line client is not the only thing that can check it: ```sh -gh release download v0.5.0 -p 'manifest.json*' +gh release download v0.3.3 -p 'manifest.json*' minisign -Vm manifest.json -P '' ``` diff --git a/compose.yaml b/compose.yaml index f62ca09..aa37f37 100644 --- a/compose.yaml +++ b/compose.yaml @@ -14,9 +14,9 @@ name: sendan services: sendan: # Both keys on purpose: `docker compose up` uses the published image, and - # `docker compose up --build` builds this checkout instead. Until a release - # is tagged there is no published image, so the second is the one that - # works. + # `docker compose up --build` builds this checkout instead. The tag follows + # the releases and carries no `v`, so pin `:0.3.3` rather than `:v0.3.3` + # for a deployment that should not move on its own. image: ghcr.io/serraniel/sendan:latest build: . restart: unless-stopped diff --git a/docs/cli.md b/docs/cli.md index fd3f32e..aa3d5a0 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -8,16 +8,9 @@ asked to obtain and trust — see [`docs/design.md`](design.md) §7.1 — so it none of the server's dependencies: no database drivers, no object storage client, no embedded web client. -> [!IMPORTANT] -> **No release has been published yet.** The release pipeline is in place and -> produces binaries and checksums for every platform below, but no tag has been -> cut, so today the only way to get `sendan` is to build it. The download -> instructions describe what a release will provide; the build instructions -> describe what works now. - ## Building it -Needs [Go](https://go.dev/dl/) 1.25 or later, and nothing else. +Needs [Go](https://go.dev/dl/) 1.26 or later, and nothing else. ```sh git clone https://github.com/Serraniel/sendan @@ -37,8 +30,8 @@ They land in `dist/`, with a `SHA256SUMS` beside them. ## Installing a release -Once a release exists, each one carries a static binary per platform. There is -nothing to install: the binary has no runtime dependency. +Each release carries a static binary per platform. There is nothing to install: +the binary has no runtime dependency. | Platform | File | |---|---| @@ -218,20 +211,16 @@ Ed25519 RWQt826yhqM+nKsvcrv1lu/eePUVXmE2haeCmGUBpgzwu7CWVZxyRUVk SLH-DSA U/vAc3e81vHgYeqfxDWYGHjGAtTZm4bz5Tz4lM42TJs= ``` -> [!IMPORTANT] -> **Nothing is signed with them yet.** `v0.1.0` is tagged but carries no -> artefacts, so `sendan verify` against that release finds nothing to check. -> Until it is rebuilt, verify with `--manifest` pointing at a manifest you -> produced yourself: +> [!TIP] +> **`--manifest` against a manifest you built yourself is the stronger check**, +> because it compares the instance to a client you produced rather than to a +> statement somebody else signed: > > ```sh > (cd web && npm ci && npm run build) > ./scripts/asset-manifest.sh /tmp/manifest.json > sendan verify https://files.example.org --manifest /tmp/manifest.json > ``` -> -> That is the stronger check anyway: it compares the instance against a client -> you built, rather than against a statement somebody else signed. The first signature is in [minisign](https://jedisct1.github.io/minisign/) format, so `minisign -Vm manifest.json -P RWQt826yhqM+nKsvcrv1lu/eePUVXmE2haeCmGUBpgzwu7CWVZxyRUVk` diff --git a/docs/deployment.md b/docs/deployment.md index f386dea..058be6e 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -4,11 +4,6 @@ Sendan is one static binary. The container image is that binary on an empty base — no distribution, no package manager, no shell. Around 17 MB, of which about 16 is the program. -> [!NOTE] -> **No image has been published yet**, because no release has been tagged. The -> pipeline that builds and signs it is in place. Until a tag exists, build the -> image yourself from a checkout — the instructions below work either way. - ## The short way [`compose.yaml`](../compose.yaml) in the repository root runs Sendan behind a @@ -22,8 +17,8 @@ That is the whole deployment. The domain must already resolve to the host, and ports 80 and 443 must reach it, because the proxy proves control of the name to get a certificate. -Until a release is tagged there is no published image, so build this checkout -instead: +That pulls the published image. To build this checkout instead — which is what +you want when running a modified tree, or to reproduce an image yourself: ```sh SENDAN_DOMAIN=files.example.org docker compose up -d --build @@ -335,7 +330,7 @@ binary of the same version are the same program. Each image is signed at release time: ```sh -cosign verify ghcr.io/serraniel/sendan:v0.5.0 \ +cosign verify ghcr.io/serraniel/sendan:0.3.3 \ --certificate-identity-regexp '^https://github\.com/Serraniel/sendan/\.github/workflows/release\.yml@' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com ``` @@ -351,7 +346,7 @@ architecture. The provenance is what ties an image to this repository and to the workflow run that produced it: ```sh -docker buildx imagetools inspect ghcr.io/serraniel/sendan:v0.5.0 \ +docker buildx imagetools inspect ghcr.io/serraniel/sendan:0.3.3 \ --format '{{ json .Provenance }}' ``` @@ -363,7 +358,7 @@ base images the build used. The bill of materials, for asking what is in the image without running it: ```sh -docker buildx imagetools inspect ghcr.io/serraniel/sendan:v0.5.0 \ +docker buildx imagetools inspect ghcr.io/serraniel/sendan:0.3.3 \ --format '{{ json .SBOM }}' ``` diff --git a/docs/design.md b/docs/design.md index 24e2461..312fd53 100644 --- a/docs/design.md +++ b/docs/design.md @@ -1,9 +1,9 @@ # Sendan — Design > [!NOTE] -> This document describes software that has not yet been implemented. It records -> decisions and their reasoning so that they need not be re-argued, and so that -> the cryptographic scheme can be reviewed before it is built. +> This document records design decisions and their reasoning, so that they need +> not be re-argued. It was written ahead of the implementation, which is why it +> reads as a plan in places; the scheme it describes is the one that is built. ## 1. Stack