From 50e5e444bc4a0c5ec0908340a3e2506981e9c517 Mon Sep 17 00:00:00 2001 From: randogoth Date: Fri, 9 Oct 2026 12:56:27 +0300 Subject: [PATCH] feat: add container deploy path (Containerfile, self-provisioning entrypoint, Forgejo registry push) --- .dockerignore | 13 +++++++++++++ Containerfile | 22 ++++++++++++++++++++++ README.md | 24 +++++++++++++++++++++--- docker-entrypoint.sh | 12 ++++++++++++ flake.nix | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 .dockerignore create mode 100644 Containerfile create mode 100644 docker-entrypoint.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1e7bf3e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +/target +/result +/result-* +/cache +/config +/fumi.rns +.git +.jj +.env +*.db +*.db-wal +*.db-shm +server.key diff --git a/Containerfile b/Containerfile new file mode 100644 index 0000000..23f11f7 --- /dev/null +++ b/Containerfile @@ -0,0 +1,22 @@ +# Default (non-rns) build only: microReticulum's cmake/C++ build isn't set +# up for the musl toolchain alpine gives us, mirroring packages.static in +# flake.nix. Use the Nix flake if you need the rns feature. +FROM rust:1-alpine AS builder +RUN apk add --no-cache musl-dev gcc +WORKDIR /usr/src/bunshin +COPY Cargo.toml Cargo.lock build.rs ./ +COPY src ./src +RUN cargo build --release --locked + +FROM alpine:3.20 +RUN apk add --no-cache ca-certificates \ + && adduser -D -h /data -u 10000 bunshin +COPY --from=builder /usr/src/bunshin/target/release/bunshin /usr/local/bin/bunshin +COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +RUN chmod +x /usr/local/bin/docker-entrypoint.sh +USER bunshin +WORKDIR /data +VOLUME /data +EXPOSE 1961/tcp +ENTRYPOINT ["docker-entrypoint.sh"] +CMD ["serve", "--key", "/data/server.key", "--db", "/data/mail.db", "--host", "0.0.0.0", "--port", "1961"] diff --git a/README.md b/README.md index c7b8b8e..9e58b34 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,29 @@ A Rust implementation of the [Smol Mail](https://code.randogoth.com/randogoth/sm The server never sees plaintext, sender identities or any private key. It learns only which mailbox an envelope is for, its size, and when it arrived. -The flake's main purpose is turnkey deployment on a NixOS host: import `nixosModules.default`, point it at a key, and `nixos-rebuild switch`. +The quickest way to run it is the container image below. For a NixOS host, the flake also provides turnkey deployment as a special case: import `nixosModules.default`, point it at a key, and `nixos-rebuild switch`. + +## Deploying with a container + +Pull the published image and bring up a server in one command: + +``` +podman run -d --name bunshin -p 1961:1961 -v bunshin-data:/data code.randogoth.com/randogoth/bunshin +``` + +`docker` works the same way — the image is a standard OCI image either way. The entrypoint generates `/data/server.key` on first run if it's missing, then runs `serve` against `/data/server.key` and `/data/mail.db` on `0.0.0.0:1961`; `podman logs bunshin` prints the generated public key to publish to clients. A key already in the volume is left alone, so restarts and upgrades keep the same identity. + +Pass your own arguments to run `keygen` or a customized `serve` instead of the default — they take the same flags as a bare-metal install, e.g. `podman run --rm -v bunshin-data:/data code.randogoth.com/randogoth/bunshin keygen --key /data/server.key --force`. + +To build the image locally instead of pulling (same non-`rns` build as the release image, see the `Containerfile` header comment): + +``` +podman build -t bunshin -f Containerfile . +``` ## Deploying on NixOS -Add bunshin as a flake input and import the module: +For a NixOS host that already manages the rest of its config with Nix, import the module instead of running the container: ```nix { @@ -34,7 +52,7 @@ Add bunshin as a flake input and import the module: } ``` -`services.bunshin` also takes `host`, `port`, `dataDir`, `maxEnvelope`, `quota`, `requestsQuota`, `retentionDays`, `requestsRetentionDays`, `maxTokens`, `rateConnections`, `rateSends`, `rateTokens`, `maxConnections` and `domains`; see `flake.nix` for defaults. The module renders a `systemd` unit that runs `bunshin serve` under `DynamicUser`; it does not generate a key. +`services.bunshin` also takes `host`, `port`, `dataDir`, `maxEnvelope`, `quota`, `requestsQuota`, `retentionDays`, `requestsRetentionDays`, `maxTokens`, `rateConnections`, `rateSends`, `rateTokens`, `maxConnections` and `domains`; see `flake.nix` for defaults. The module renders a `systemd` unit that runs `bunshin serve` under `DynamicUser`; unlike the container entrypoint, it does not generate a key. For the RNS carrier, build `packages.rns`, set `services.bunshin.package` to it, and enable `services.bunshin.rns` with its `keyFile`; see [RNS.md](RNS.md) for the protocol and the remaining options. diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..40d2a18 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# Generates the server key on first run so a bare `docker run` against an +# empty volume works; a key already at BUNSHIN_KEY is left untouched. +set -e + +: "${BUNSHIN_KEY:=/data/server.key}" + +if [ "$1" = "serve" ] && [ ! -f "$BUNSHIN_KEY" ]; then + bunshin keygen --key "$BUNSHIN_KEY" +fi + +exec bunshin "$@" diff --git a/flake.nix b/flake.nix index 2b58953..29158a9 100644 --- a/flake.nix +++ b/flake.nix @@ -156,6 +156,38 @@ }; }; + # Builds the Containerfile image (same non-rns build the release + # binary is, see its header comment) and pushes it to this repo's + # Forgejo container registry, tagged by short commit hash and + # `latest`. Needs FORGEJO_TOKEN (a token with write:package scope) + # in the environment; run from a checkout so `git rev-parse` and + # the Containerfile build context resolve to the right place. + apps.release-container = flake-utils.lib.mkApp { + drv = pkgs.writeShellApplication { + name = "bunshin-release-container"; + runtimeInputs = [ pkgs.podman pkgs.git ]; + text = '' + if [ -f .env ]; then + set -a + # shellcheck disable=SC1091 + . ./.env + set +a + fi + : "''${FORGEJO_TOKEN:?set FORGEJO_TOKEN (env or .env) to a Forgejo token with write:package scope}" + + rev=$(git rev-parse --short HEAD) + registry="code.randogoth.com/randogoth/bunshin" + + echo "''${FORGEJO_TOKEN}" | podman login code.randogoth.com -u randogoth --password-stdin + podman build -f Containerfile -t "$registry:$rev" -t "$registry:latest" . + podman push "$registry:$rev" + podman push "$registry:latest" + + echo "pushed: https://code.randogoth.com/randogoth/-/packages/container/bunshin" + ''; + }; + }; + devShells.default = pkgs.mkShell { packages = with pkgs; [ cargo rustc rustfmt clippy pkg-config gcc sqlite cmake ninja ]; env = rnsSourceEnv;