.forge/wiki/deploy 5.2 KiB 99 lines Raw
1 # Deploy hu13 with Docker2 3 ## Build the image4 5 From the repo root (build context is sources only — see `.dockerignore`):6 7 ```sh8 docker build -t hu13 .9 ```10 11 Multi-stage: `rust:1.98-bookworm` compiles with12 `cargo build --locked --release` (only `Cargo.toml`, `Cargo.lock`, `src/`,13 `static/` are copied, so edits elsewhere don't bust the cache);14 `debian:bookworm-slim` runtime adds `ca-certificates`, a nonroot user15 `hu13` (uid 10000), the binary, and `entrypoint.sh`. No toolchain, no16 sources, no `.git` in the final image.17 18 ## Run it19 20 ```sh21 docker run --rm -p 39313:39313 \22   -v "$PWD:/data/repo:ro" \23   -v hu13-inbox:/data/inbox \24   -e HU13_BRANCH=main \25   -e HU13_PUBLIC_ORIGIN=http://localhost:39313 \26   -e HU13_CLONE_URL=https://example.com/mirror.git \27   hu1328 ```29 30 Then open `http://localhost:39313/`.31 32 What happens inside (`entrypoint.sh`):33 34 1. `hu13 build /data/repo -o /srv/www` (+ `--branch` / `--clone-url` /35    `--public-origin` only when `HU13_BRANCH` / `HU13_CLONE_URL` /36    `HU13_PUBLIC_ORIGIN` are set). Any failure — bad branch, unreadable37    repo — aborts LOUD before serving starts (`set -eu`; serve never runs38    on a stale/empty site). The build emits `repo.git` (a dumb-HTTP bare39    repo: `git clone <origin>/repo.git`, no smart server needed),40    `repo.bundle` and `repo.tar.gz`.41 2. `exec hu13 serve --www /srv/www --inbox-dir /data/inbox42    --listen 0.0.0.0:39313` (+ `--max-title`, `--max-body`,43    `--max-bytes`, `--rate-per-hour`, `--quota` only when the matching44    `HU13_*` env var is set; otherwise the serve defaults apply).45    `exec` keeps `serve` on PID 1.46 47 ## Volumes / env reference48 49 | Mount / var            | Default            | Notes                                              |50 | ---------------------- | ------------------ | -------------------------------------------------- |51 | `/data/repo` (ro)      | —                  | repo checkout to render (required)                 |52 | `/data/inbox`          | —                  | inbox storage; MUST sit outside `--www` (serve refuses otherwise) |53 | `HU13_BRANCH`          | `main`, else `master` | branch to build; wrong name fails the container |54 | `HU13_PUBLIC_ORIGIN`   | unset              | absolute base URL; enables `git clone <origin>/repo.git` + absolute bundle/tarball links |55 | `HU13_CLONE_URL`       | git origin remote  | fallback/mirror label shown as "mirror source"     |56 | `HU13_LISTEN`          | `0.0.0.0:39313`    | serve address                                      |57 | `HU13_MAX_TITLE`       | `300`              | inbox title cap (chars)                            |58 | `HU13_MAX_BODY`        | `20000`            | inbox body/prompt cap (chars)                      |59 | `HU13_MAX_BYTES`       | `32768`            | whole-POST cap (bytes)                             |60 | `HU13_RATE_PER_HOUR`   | `2`                | per-IP POSTs/hour                                  |61 | `HU13_QUOTA`           | `500`              | global inbox file quota                            |62 63 ## Notes64 65 - `dist/` in the repo is gitignored; build outside the checkout (or to66   `/srv/www`) so the derived `repo.git` directory never looks tracked.67 - Static hosting (Cloudflare Pages/nginx): publish the whole `dist/`68   directory. `dist/repo.git` is plain files + `update-server-info`, and69   `dist/_headers` carries the `/raw/**` and artifact70   `/releases/<version>/**` download policy for Pages. On nginx, add the71   equivalent `add_header Content-Disposition attachment` for `/raw/` and72   release artifact directories — committed HTML/SVG must never render.73 - Pages clean-URL redirects: Pages answers `.html` paths with a 30874   (`/issues/index.html` → `/issues/`, `/files/a.md.html` →75   `/files/a.md`); the links still resolve, and Docker/nginx serve the76   exact path. `_redirects` status 200 cannot override this. If exact URL77   shape matters, use a proxy/Worker or accept the Pages form.78 - Cloudflare Pages inbox: the static `dist/` alone has **no inbox**. The79   Pages Function in `functions/inbox/[kind].js` + R2 binding `INBOX` and80   secret `INBOX_SALT` implement the same contract. See81   `docs/cloudflare.md` for the bucket, binding, secret and WAF rate-rule82   recipe. `dist/_routes.json` limits Functions to `/inbox/*`.83 84 - Binary size: release build is ~2.1MB unstripped, ~1.7MB stripped.85   If it ever tops 2MB stripped, the cut list is: replace `yaml_serde`86   (libyaml) with the hand-rolled frontmatter split already used for87   parsing shapes, hand-roll the fixed-order `api/*.json` emitter instead88   of `serde_json`, enable `strip = true` + `opt-level = "z"` + LTO in the89   release profile. Do not gold-plate before then.90 - `HU13_WWW` / `HU13_INBOX_DIR` also work for bare-metal91   `hu13 serve` (flags win over env); the container always passes92   explicit `--www` / `--inbox-dir`.93 - Inbox files are read locally only: `hu13 inbox list|show --inbox-dir94   /data/inbox` (or `docker exec` into the volume). Never served over HTTP.95 - Mount a plain checkout, not a git worktree: a worktree's `.git` is a96   pointer file (`gitdir: ...`) whose target lives outside the mount, so97   git inside the container cannot resolve anything. `git clone` the98   branch (or copy the repo) and mount that. (Host/container uid mismatch99   on the mount is already handled via `safe.directory` in entrypoint.sh.)