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/) →