# Install paths and share directory

> PGRUST_PGSHAREDIR and related build-time path env vars, runtime -L override, vendored share tree contents, and why wrong share/tz paths break boot.

- Repository: malisper/pgrust
- GitHub: https://github.com/malisper/pgrust
- Human docs: https://grok-wiki.com/public/docs/malisper-pgrust-7e0c8e82bc21
- Complete Markdown: https://grok-wiki.com/public/docs/malisper-pgrust-7e0c8e82bc21/llms-full.txt

## Source Files

- `.cargo/config.toml`
- `crates/backend/utils/init/miscinit/src/boot_paths.rs`
- `crates/backend/main/initdb/src/lib.rs`
- `Dockerfile`
- `vendor/postgres-18.3/share/postgres.bki`
- `vendor/postgres-18.3/share/postgresql.conf.sample`

---

---
title: "Install paths and share directory"
description: "PGRUST_PGSHAREDIR and related build-time path env vars, runtime -L override, vendored share tree contents, and why wrong share/tz paths break boot."
---

pgrust bakes PostgreSQL-style install directories into the `postgres` binary at compile time via `option_env!("PGRUST_*")` in `boot_paths`, then derives a runtime share path with `get_share_path(my_exec_path)`. Initdb can override that path for bootstrap templates with `-L`; timezone data, abbreviation sets, extension control files, and tsearch dictionaries still resolve through the baked-in share path at server runtime.

## Path model

```text
Build-time env (option_env!)
  PGRUST_PGSHAREDIR, PGRUST_PGBINDIR, PGRUST_PKGLIBDIR, ...
        │
        ▼
  Binary literals (defaults: /usr/local/pgsql/{share,bin,lib,...})
        │
        ▼
  get_share_path(my_exec_path)
    make_relative_path(PGSHAREDIR, PGBINDIR, exec)
        │
        ├─ initdb: sharedir (or -L override)
        │     postgres.bki, *.conf.sample, system_*.sql, ...
        │
        └─ server runtime (always derived, not -L)
              share/timezone       → pg_TZDIR / pg_open_tzfile
              share/timezonesets   → read_tz_file
              share/extension      → CREATE EXTENSION control/SQL
              share/tsearch_data   → text search dictionaries
```

| Concern | Who sets path | Mechanism |
|---------|---------------|-----------|
| Share / bindir / pkglib / etc. literals | Build | `PGRUST_*` env → `option_env!` |
| Resolved share path | Boot / initdb | `get_share_path` + `my_exec_path` |
| Initdb templates only | CLI | `-L <sharedir>` |
| Docker image share | Image build + entrypoint | `PGRUST_PGSHAREDIR` bake + `-L "$PG_SHAREDIR"` |

<Warning>
`PGRUST_PGSHAREDIR` is **compile-time only**. Exporting it when *running* the binary does not relocate share/tz paths. Rebuild with the desired value (or pass `-L` for initdb template loading only).
</Warning>

## Build-time path environment variables

`crates/backend/utils/init/miscinit/src/boot_paths.rs` mirrors PostgreSQL’s configure-time install dirs (`pg_config_paths.h`). Each value is read with `option_env!("PGRUST_…")` and falls back to the documented `/usr/local/pgsql/...` layout.

| Env var | Default | Used by |
|---------|---------|---------|
| `PGRUST_PGSHAREDIR` | `/usr/local/pgsql/share` | `get_share_path` → tz, timezonesets, extensions, tsearch, initdb default |
| `PGRUST_PGBINDIR` | `/usr/local/pgsql/bin` | Relativization base for all `make_relative_path` targets |
| `PGRUST_PKGLIBDIR` | `/usr/local/pgsql/lib` | `get_pkglib_path` / `pkglib_path` (standalone boot) |
| `PGRUST_SYSCONFDIR` | `/usr/local/pgsql/etc` | `get_etc_path` / `SYSCONFDIR` config row |
| `PGRUST_LIBDIR` | `/usr/local/pgsql/lib` | `get_lib_path` |
| `PGRUST_INCLUDEDIR` | `/usr/local/pgsql/include` | `get_include_path` |
| `PGRUST_PKGINCLUDEDIR` | `/usr/local/pgsql/include` | `get_pkginclude_path` |
| `PGRUST_INCLUDEDIRSERVER` | `/usr/local/pgsql/include/server` | `get_includeserver_path` |
| `PGRUST_LOCALEDIR` | `/usr/local/pgsql/share/locale` | `get_locale_path` |
| `PGRUST_DOCDIR` | `/usr/local/pgsql/share/doc` | `get_doc_path` |
| `PGRUST_HTMLDIR` | `/usr/local/pgsql/share/doc` | `get_html_path` |
| `PGRUST_MANDIR` | `/usr/local/pgsql/share/man` | `get_man_path` |

`PGRUST_PGSHAREDIR` is the high-impact setting: wrong bake-in points timezone and abbreviation loading at a nonexistent or incomplete tree.

### Repository defaults

`.cargo/config.toml` sets:

```toml
[env]
PGRUST_PGSHAREDIR = "/tmp/pgrust_share"
```

Cargo applies this only when the variable is **unset** in the process environment (`force` is not enabled). Explicit:

```bash
PGRUST_PGSHAREDIR=/somewhere cargo build
```

still wins.

README’s source-build recipe bakes the vendored tree instead:

```bash
PGRUST_PGSHAREDIR="$PWD/vendor/postgres-18.3/share" \
cargo build --release --locked --bin postgres
```

| Build context | Typical `PGRUST_PGSHAREDIR` |
|---------------|----------------------------|
| Plain `cargo build` (repo config) | `/tmp/pgrust_share` |
| Documented local release build | `$PWD/vendor/postgres-18.3/share` |
| Docker `rustbuild` stage | `/usr/share/postgresql/18` (`ARG PG_SHAREDIR`) |

Ensure the path you bake actually exists and contains `timezone/`, `timezonesets/`, and (for initdb) the bootstrap files listed below—or pass a correct `-L` at initdb time and keep the bake-in correct for server runtime.

### Relative vs absolute resolution

`get_share_path(my_exec_path)` calls `make_relative_path(PGSHAREDIR, PGBINDIR, my_exec_path)`:

1. If the running binary’s directory layout still matches the common prefix of the configured bindir/sharedir, the share path is **relocated** relative to the executable.
2. Otherwise the result is the **configured absolute** `PGSHAREDIR` (after canonicalize).

Docker installs the binary at `/usr/local/bin/pgrust-postgres`, outside a PGDG-style bindir, so relative derivation does **not** find the share tree—entrypoint always passes `-L` for initdb, and the image bakes `PGRUST_PGSHAREDIR` to the absolute PGDG share path for runtime tz.

Wasm builds: `find_my_exec` returns the sentinel `/postgres` (no real executable on disk); share is expected to come from the bake-in / host VFS.

## Runtime `-L` (initdb only)

`postgres --initdb` / `postgres initdb` parses `-L` as a share-directory override:

<ParamField body="-L" type="path" required={false}>
Share directory used for bootstrap templates and post-bootstrap SQL. When omitted, initdb uses `get_share_path(current_exe)`.
</ParamField>

```bash
target/release/postgres --initdb \
  -D /tmp/pgrust-data \
  -L "$PWD/vendor/postgres-18.3/share" \
  --no-locale \
  --encoding UTF8 \
  -U postgres
```

| Phase | Reads from `sharedir` |
|-------|------------------------|
| Config scaffolding | `postgresql.conf.sample`, `pg_hba.conf.sample`, `pg_ident.conf.sample` |
| Bootstrap (`--boot`) | `postgres.bki` (header must be `# PostgreSQL 18`) |
| Post-bootstrap (`--single`) | `system_constraints.sql`, `system_functions.sql`, `system_views.sql`, `snowball_create.sql`, `information_schema.sql`, `sql_features.txt` (via `COPY`) |

Missing or wrong `-L` surfaces as read failures such as `could not read "<sharedir>/postgres.bki": ...` or a BKI header mismatch for a non-18 tree.

<Note>
`-L` does **not** redirect the postmaster’s timezone or extension share lookups. Those always use `get_share_path` from the compiled-in dirs + `my_exec_path`.
</Note>

Docker entrypoint (always explicit `-L`):

```bash
"$PGRUST_BIN" --initdb -D "$PGDATA" -L "$PG_SHAREDIR" --username "$POSTGRES_USER" ...
```

with `PG_SHAREDIR="${PG_SHAREDIR:-${PGRUST_PGSHAREDIR:-/usr/share/postgresql/18}}"`.

## Vendored share tree

Clone-local share data lives at `vendor/postgres-18.3/share` (built/installed `share/postgresql/` from PostgreSQL 18.3, not raw source). Documented purpose: bootstrap + runtime data without an external PG install tree.

:::files
vendor/postgres-18.3/share/
  postgres.bki                 # catalog bootstrap input (~1MB built BKI)
  system_constraints.sql
  system_functions.sql
  system_views.sql
  information_schema.sql
  sql_features.txt
  snowball_create.sql
  postgresql.conf.sample
  pg_hba.conf.sample
  pg_ident.conf.sample
  pg_service.conf.sample
  psqlrc.sample
  errcodes.txt
  timezone/                    # IANA tzdb tree (America/, Europe/, ...)
  timezonesets/                # Default, Australia, India, *.txt region sets
  tsearch_data/                # text search dictionaries / stopwords
  extension/                   # *.control + versioned extension SQL
:::

| Subtree | Consumer |
|---------|----------|
| Root `*.bki` / `system_*.sql` / samples | `--initdb` bootstrap and config setup |
| `timezone/` | `pg_tzdir()` → `<share>/timezone`; `pg_open_tzfile` |
| `timezonesets/` | `read_tz_file` → `<share>/timezonesets/<name>` |
| `extension/` | `get_extension_control_directories` → `<share>/extension` |
| `tsearch_data/` | tsearch loaders via `get_share_path` |

`postgres.bki` first line is `# PostgreSQL 18`. Initdb rejects a share tree whose header does not match major version `18`.

### Docker share layout differences

The image does not ship the git-vendored tree. Stage `pgtools` installs PGDG `postgresql-18` and copies `/usr/share/postgresql/18` into the final image. Debian PG is built `--with-system-tzdata`, so the package share has **no** `timezone/` directory; the Dockerfile links it:

```dockerfile
ln -snf /usr/share/zoneinfo "${PG_SHAREDIR}/timezone"
```

The rustbuild stage sets `ENV PGRUST_PGSHAREDIR=${PG_SHAREDIR}` so the binary’s runtime tz path matches that location.

## Consumers of the share path

| Component | Path formula | Failure if wrong |
|-----------|--------------|------------------|
| Initdb (default) | `get_share_path(exe)` or `-L` | No BKI / samples / system SQL |
| Timezone names | `get_share_path` + `/timezone` | Zone load fails; session/log timezone broken |
| Abbreviation sets | `get_share_path` + `/timezonesets/<file>` | `DirectoryMissing` / file open errors on incomplete install |
| Extensions | `get_share_path` + `/extension` | Missing `.control` / script SQL |
| Full-text search | `get_share_path` + `/tsearch_data/...` | Dictionary / config file not found |

`common_path_seams::get_share_path` is installed from miscinit (`boot_paths::get_share_path`). Timezone code memoizes `pg_TZDIR` once per backend.

## Why wrong share/tz paths break boot

1. **Initdb cannot scaffold catalogs** without a readable `postgres.bki` and post-bootstrap SQL under the effective share dir.
2. **Server boot and SQL that touch timestamps/timezones** open files under `<share>/timezone`. A baked path pointing at `/usr/local/pgsql/share` (default when `PGRUST_PGSHAREDIR` was never set) or an empty `/tmp/pgrust_share` yields open failures.
3. **Abbreviation loading** probes `<share>/timezonesets`; if the directory is missing, `TzFileOpenError::DirectoryMissing` carries the directory path and `my_exec_path` for the incomplete-install diagnostic path.
4. **Relocated binaries** without matching bindir/sharedir layout fall back to the absolute baked share path—if that absolute path is wrong on the host, nothing relocates it.
5. **Docker without `-L`** would initdb against a derived path that cannot find PGDG share; the entrypoint therefore always passes `-L`.

<Check>
After a successful bake and initdb, verify:

- `test -f "$SHARE/postgres.bki" && head -1 "$SHARE/postgres.bki"` → `# PostgreSQL 18`
- `test -d "$SHARE/timezone" && test -d "$SHARE/timezonesets"`
- Server starts with required stack/`io_method` settings and `SELECT now();` succeeds
</Check>

## Practical recipes

<Tabs>
<Tab title="Local clone (README)">
```bash
PGRUST_PGSHAREDIR="$PWD/vendor/postgres-18.3/share" \
  cargo build --release --locked --bin postgres

target/release/postgres --initdb \
  -D /tmp/pgrust-data \
  -L "$PWD/vendor/postgres-18.3/share" \
  --no-locale --encoding UTF8 -U postgres
```
Bake and `-L` both point at the vendored tree so initdb templates and runtime tz agree.
</Tab>
<Tab title="Default cargo config">
```bash
# .cargo/config.toml → /tmp/pgrust_share
# Populate or symlink first:
mkdir -p /tmp/pgrust_share
# e.g. rsync or ln -s the vendored share contents

cargo build --release --locked --bin postgres
```
Must keep `/tmp/pgrust_share` populated; the default is only a path string, not an auto-populated tree.
</Tab>
<Tab title="Docker">
```bash
docker build -t pgrust .
# Image bakes PGRUST_PGSHAREDIR=/usr/share/postgresql/18
# entrypoint always: --initdb -L "$PG_SHAREDIR"
docker run --rm -e POSTGRES_PASSWORD=secret -p 5432:5432 pgrust
```
</Tab>
</Tabs>

## Failure modes (share/path)

| Symptom | Likely cause | Fix |
|---------|--------------|-----|
| `could not read ".../postgres.bki"` | Missing/wrong `-L` or empty derived share | Pass `-L` to vendored or image share |
| BKI header mismatch | Share from wrong PG major | Use PostgreSQL 18 share (`# PostgreSQL 18`) |
| Timezone open failures / incomplete install hint | Baked `PGRUST_PGSHAREDIR` wrong or tree lacks `timezone/` / `timezonesets/` | Rebuild with correct path; ensure tree complete (Docker: zoneinfo symlink) |
| Extension control not found | Share lacks `extension/` or path derives elsewhere | Point bake-in at tree that includes `extension/`; see contrib docs |
| Works after rebuild only | Changed env at runtime only | Rebuild—paths are compile-time |

## Related pages

<CardGroup cols={2}>
  <Card title="Build from source" href="/build-from-source">
    Host packages, bake-in, `cargo build --release --locked --bin postgres`.
  </Card>
  <Card title="Initialize a cluster" href="/init-cluster">
    `--initdb` phases, directory layout, success text.
  </Card>
  <Card title="initdb options reference" href="/initdb-reference">
    Full `-L` / `-D` / locale flag set and unsupported options.
  </Card>
  <Card title="Configuration reference" href="/configuration-reference">
    Runtime GUCs plus build-time `PGRUST_*` path vars.
  </Card>
  <Card title="Docker" href="/docker">
    Image contract and first-boot entrypoint behavior.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Wrong share/tz path among known failure modes.
  </Card>
</CardGroup>
