postjevsql.git / nix / README.md
1# Chapter 16: nix, packages, a module and an image
2
3Building a Postgres extension is easy on your own machine and surprisingly
4hard to do *reproducibly*: it compiles against one Postgres major's headers,
5it needs a C toolchain and libclang for the bindings, and it needs a few
6hundred crates from crates.io. Nix is how this repository pins every one of
7those, and this folder is everything nix builds from it: the extension for
8each supported major, the sidecar CLI, a NixOS module that runs a sidecar,
9a container image for everyone without NixOS, and two virtual machines that
10check the last two actually work.
11
12The trick in `package.nix` is that it does not use the usual pgrx builder.
13The crates here exist only as buck targets, so building with cargo-pgrx would
14need a second, hand-kept Cargo graph beside them. Instead, the package runs
15**buck2 inside the nix sandbox** and builds the very target the tests build,
16`//crates/postjevsql:ext`.
17
18```mermaid
19flowchart TD
20  B["third-party/BUCK: every crate's url and sha256"] -->|"read at eval"| F["nix fetches each crate (fetchurl)"]
21  F --> S["the sandbox: no network"]
22  O["offline_archive.bzl, loaded over http_archive"] --> S
23  S --> X["buck2 build //crates/postjevsql:ext --target-platforms //platforms:pgNN"]
24  X --> P["lib/postjevsql.so and share/postgresql/extension/, in nixpkgs' layout"]
25  P --> PK["postgresqlNNPackages.postjevsql"]
26  PK --> M["nixosModules.sidecar"]
27  PK --> I["postjevsql-sidecar-image"]
28```
29
30> **Aside: three small lies to the sandbox.** buck2 was not written to run
31> inside a nix build, and three things had to be smoothed over. Its
32> downloader refuses `file://` URLs, so a tiny rule extracts the crates nix
33> already fetched. It loads a trust store at startup even though nothing is
34> downloaded, so `SSL_CERT_FILE` points at one. And the prelude's scripts
35> start `#!/usr/bin/env bash`, which the sandbox lacks, so buck runs under
36> bubblewrap with that one path added.
37
38> **Try it.** `nix build -L .#checks.x86_64-linux.sidecar` boots a VM with
39> the sidecar module and a stand-in managed database (TLS and SCRAM only) and
40> checks that `CREATE EXTENSION postjevsql` works, the target's tables are
41> imported and readable, a `jev` call plans as the scan over a
42> `ForeignScan`, a restart picks up a new target column, and the password
43> never reaches the nix store.
44
45The flake's outputs, all for `x86_64-linux`:
46
47| Output | What |
48| --- | --- |
49| `legacyPackages.postgresql17Packages.postjevsql`, `…postgresql18Packages.postjevsql` | The extension per major, named as nixpkgs names its sets. |
50| `packages.default` | The extension for the newest major. |
51| `packages.postjevsql-sidecar` | The sidecar CLI. |
52| `packages.postjevsql-sidecar-image` | The sidecar as a `docker load`-able image. |
53| `nixosModules.sidecar` | `services.postjevsql-sidecar`: a local Postgres with the extension and postgres_fdw, and a oneshot that runs the CLI. |
54| `checks.sidecar`, `checks.sidecar-image` | The module, and the image in docker, each booted in a VM against the same target. |
55| `devShells.default` | buck2, reindeer, rustc, clippy, clang and bindgen's hook, the newest Postgres on `PATH`, and a `shellHook` that writes the tools' store paths, and every supported major's server and `pg_config`, into `.buckconfig.local`. |
56
57## For the people who maintain it
58
59| File | What |
60| --- | --- |
61| [package.nix](package.nix) | The extension for one major: the crates fetched from `third-party/BUCK`'s records, `src` as a fileset of only what the build reads (docs excluded), buck2 under bwrap, and nixpkgs' layout on install. `passthru.buck` builds another target the same way. |
62| [majors.nix](majors.nix) | The supported majors, read from `PG_MAJORS` in `build/defs.bzl`, the one place they are written. |
63| [offline_archive.bzl](offline_archive.bzl) | The sandbox's stand-in for buck's `http_archive`: extracts a crate nix fetched. Unused outside the package build. |
64| [sidecar-cli.nix](sidecar-cli.nix) | The `postjevsql-sidecar` binary, built by `package.nix`'s derivation with another target, so the two cannot drift. |
65| [sidecar.nix](sidecar.nix) | The NixOS module: `services.postgresql` with the extension and postgres_fdw, and `postjevsql-sidecar.service`, which writes one TOML per server and runs `converge` then `sync --apply`. Passwords reach it as systemd credentials. |
66| [sidecar-image.nix](sidecar-image.nix) | The OCI image: PostgreSQL 18, the extension, postgres_fdw and the CLI, whose `serve` is the entrypoint; config and secrets are mounted, never built in. |
67| [sidecar-test.nix](sidecar-test.nix) | The flake check `sidecar`: the module in a VM. |
68| [sidecar-image-test.nix](sidecar-image-test.nix) | The flake check `sidecar-image`: the image in docker in a VM, including a secret given twice stopping the container with the CLI's refusal. |
69| [test-target.nix](test-target.nix) | The stand-in managed database both checks use: Postgres 18 on loopback port 5433, TLS and SCRAM only, with build-time test certificates. |
70
71← Previous: [Chapter 15, fixtures/](../tests/fixtures/) · Up: [postjevsql](../) · Next: [Chapter 17, tools/](../tools/) →