# Local development and testing

## Start a server

### Local prerequisites

Local development needs Erlang/OTP, a C compiler and `make`, OpenSSH, and
Rebar3, Node.js 22+ for frontend tests, Docker for the test database, and
Chrome/Chromium for executable browser integration tests. On macOS, install Erlang and the Apple command-line tools first:

```sh
brew install erlang
xcode-select -p
```

If `xcode-select -p` reports that no developer tools are installed, run
`xcode-select --install` once and complete the installer.

The project uses [rebar3](https://rebar3.org/) for dependency resolution,
development builds, and production releases. Initialize the pinned native
dependency, then install Rebar3 globally or with the repository-compatible
installer:

```sh
make submodules-init
make install-rebar3
```

The installer places Rebar3 in `~/.cache/rebar3/bin/rebar3`; the server
Makefile discovers it there, so it does not need to be added to `PATH`.

### Create a dedicated development identity

Do not reuse your everyday SSH identity for the local server. Generate a key
used only by Bibleit development:

```sh
ssh-keygen -t ed25519 \
  -f ~/.ssh/bibleit_local_dev \
  -C "bibleit-local-dev"
```

This creates the private key `~/.ssh/bibleit_local_dev` and the public key
`~/.ssh/bibleit_local_dev.pub`. Only the public key is passed to or stored by
Bibleit. Keep the private key private and do not commit either file.

Account SSH keys support Ed25519 (recommended), RSA with at least 2048 bits
(3072+ recommended for new keys), and ECDSA on NIST P-256, P-384, or P-521.
RSA authentication requires SHA-256 or SHA-512 signatures; legacy RSA/SHA-1,
DSA, security-key (`sk-*`) types, and SSH certificates are not supported.

### Build, test, and run

Verify the server using the separate disposable test PostgreSQL service:

```sh
make test-db-up
make test                 # Delegates to rebar3 check
make test-db-down         # Remove the test service when finished
```

Start the development database separately with `make db-up`.

See [backup/recovery](backup-recovery.md) for the isolated combined-fault and
restoration regressions and the offline restored-credential quarantine procedure.

Extended local measurements use the same disposable database and fixture-specific
limits. Run the suites sequentially (Common Test does not accept multiple suites
with a shared `--case` selection):

```sh
BIBLEIT_LOAD_SECONDS=120 BIBLEIT_LOAD_REPORT=/tmp/bibleit-load.json \
  make rebar3 ARGS='ct --suite=test/bibleit_infrastructure_SUITE --case=sustained_load'
BIBLEIT_FANOUT_SECONDS=120 BIBLEIT_LARGE_CHURN_CYCLES=30 \
  make rebar3 ARGS='ct --suite=test/bibleit_stream_resilience_SUITE --case=large_reconnect_churn,sustained_fanout'
```

The stream cases use 48 persistent authenticated clients and 96-client reconnect
bursts around 24 persistent streams. All sockets share one loopback source IP;
this does not measure distributed-IP traffic or production capacity. Delivery
latencies, process/binary-memory peaks, admission results and recovery assertions
are recorded in Common Test logs. Default durations remain short for `make test`.


Start the server:

```sh
make run
```

`make run` incrementally builds `libbibleit`, the NIF, and Erlang modules, then
starts the HTTP server on port `8080` and the SSH server on port `2222`. It
also creates a local SSH host key under `priv/ssh/` on first use.
Translation files and that host key default to `.data/`; application
state lives in PostgreSQL. Press `Ctrl-C` to stop the server.

Create or sign into an account at `http://localhost:8080`, then add the contents
of `~/.ssh/bibleit_local_dev.pub` in the dashboard. SSH rejects the key until
it has been linked there. Afterward, connect with the dedicated identity:

```sh
ssh -p 2222 \
  -i ~/.ssh/bibleit_local_dev \
  -o IdentitiesOnly=yes \
  bibleit-cli@127.0.0.1
```

The first direct `ssh` connection asks you to trust the server's local host
key.  The SSH username is
ignored for identity purposes: Bibleit resolves the presented public key to
its account. Once connected, verify the account and translation library:

```text
whoami
account info
account translation add WEB
account translation list
account quota list
```

For a shorter connection command, add this optional alias to `~/.ssh/config`:

```sshconfig
Host bibleit-local
  HostName 127.0.0.1
  Port 2222
  User bibleit-cli
  IdentityFile ~/.ssh/bibleit_local_dev
  IdentitiesOnly yes
```

Then connect with `ssh bibleit-local`.

`make shell` opens an Erlang shell with the application and its
dependencies on the code path.

Environment variables supplied to `make run` are passed to runtime configuration. For
example, test Google sign-in locally with:

```sh
BIBLEIT_GOOGLE_CLIENT_ID=... \
BIBLEIT_GOOGLE_CLIENT_SECRET=... \
BIBLEIT_PUBLIC_URL=http://localhost:8080 \
make run
```

### PostgreSQL

PostgreSQL is required. `make db-up` starts the repository's loopback-only
Docker service and the Makefile supplies its local `DATABASE_URL`. The server
uses a pool of four connections by default; override it with
`BIBLEIT_DATABASE_POOL_SIZE`.

```sh
make db-up
make run
```

Open an interactive PostgreSQL shell with:

```sh
docker compose -f docker-compose.local.yaml exec postgres psql -U bibleit -d bibleit
```

For a custom database, copy `.env.example` to `.env` and keep that file local.
Bibleit loads it automatically at startup; values already set in the process
environment take precedence. The committed Compose password is intentionally
only for a database bound to `127.0.0.1`; never reuse it in a deployed
environment.

For local email/password testing, add Resend credentials and an authorized
sender to the same command:

```sh
BIBLEIT_RESEND_API_KEY=re_... \
BIBLEIT_EMAIL_FROM='Bibleit <accounts@your-domain.example>' \
BIBLEIT_PUBLIC_URL=http://localhost:8080 \
make run
```

After adding the public key in the dashboard, connect through the native SSH
endpoint with the same local key:

```sh
ssh -p 2222 -i ~/.ssh/bibleit_local_dev -o IdentitiesOnly=yes bibleit-cli@127.0.0.1
# Then run: whoami
```

### Browser pages

- `/` is the product landing page.
- `/docs` starts with the dashboard quickstart and links to guides, protocol
  and HTTP reference, troubleshooting, developer docs, and Bibleit Shell.
- `/plans` compares Starter, Contributor, and Organization capabilities.
- `/contribute` explains qualifying contributions and guides visitors to create a Starter account.
- `/settings/plan` lets Starter users request Contributor recognition and track private
  review status and feedback. New users land here after choosing a username.
- `/plans/organization` accepts requests for custom capacity, member seats, and design.
- `/organizations` lists your organizations and their shared workspaces.
- `/support` associates GitHub issues with guaranteed maintainer review.
- `/dashboard` is the authenticated account overview. Its account menu links
  back to plans and documentation.

Accounts start on Starter: 3 enabled translations, 1 stored Live, 1 personal
access token, 1 SSH key, 1 collaboration slot per Live, and 50 viewer connections
per Live. Contributor is awarded after maintainer review of meaningful code or non-code
contributions; it includes 5 translations, 5 Lives, 3 tokens, 3 SSH keys,
3 collaboration slots per Live, 250 viewer connections, and guaranteed GitHub
issue review. Organization capacity and custom Live design require manual approval.
Organizations own their Lives. Owners and admins manage membership; operators
create and operate Lives, and viewers have read-only access. Resource limits are
assigned per member within approved ceilings. Personal accounts remain separate.
Contributor has no expiry or ongoing contribution obligation. See
[the contribution guide](https://github.com/bibleit-labs/bibleit-server/blob/main/CONTRIBUTING.md) for examples and recognition criteria.

## Versioning

[`VERSION`](https://github.com/bibleit-labs/bibleit-server/blob/main/VERSION) is the single version source for `bibleit_server` and its
OTP release. To make a release, change that file to the next semantic version,
then build and verify it:

```sh
make version
# edit VERSION, for example: 0.0.2
make test
make release
```

The resulting artifact is named `bibleit_server-<version>` under
`_build/prod/rel/`. The application also reports the same version through
`server info`.

### Testing strategy

Rebar3 owns test compilation, discovery, and execution. `make test` is a thin
wrapper for `rebar3 check`; there is no manually maintained test module list.
When Rebar3 is installed only in its local cache, use `make rebar3 ARGS="..."`
for any command below.

| Command | Coverage | Dependencies |
| --- | --- | --- |
| `rebar3 eunit` | Fast Erlang tests, followed by JavaScript and native C tests | Node.js 22+, native build tools; no running database |
| `rebar3 ct` | Integration, security, resilience and real-browser regression suites | Disposable PostgreSQL service, Node.js 22+, Chrome/Chromium |
| `rebar3 ct --suite=test/bibleit_browser_security_SUITE.erl` | Browser XSS, caching, framing and response-header policy | Disposable PostgreSQL, Node.js 22+, Chrome/Chromium |
| `rebar3 ct --suite=test/bibleit_route_limits_SUITE.erl` | Dynamic-route and reader admission, proxy grouping, restart and independent-node budgets | Disposable PostgreSQL, Erlang runtime (two bounded child VMs) |
| `rebar3 ct --suite=test/bibleit_fuzz_SUITE.erl` | Seeded protocol/query/ZIP mutation corpora and import boundaries | Disposable PostgreSQL, native library |
| `rebar3 check` | Both commands above, with failures propagated | All test dependencies |
| `rebar3 ct --suite=test/bibleit_live_persistence_SUITE.erl` | Live state, credentials, collaborators, and revisions across actor restarts | Disposable PostgreSQL service |
| `rebar3 ct --suite=test/bibleit_regression_SUITE.erl --group=bibleit_protocol_tests` | One legacy integration module | Disposable PostgreSQL service |

EUnit discovers fast test modules directly under `test/unit/`. Keep these free of
PostgreSQL and external services. Existing integration tests retain their
`test/auth`, `test/http`, `test/live`, `test/protocol`, `test/ssh`, and
`test/translations` locations during migration. `bibleit_regression_SUITE`
discovers their `*_tests.erl` source files and runs each module as a separately
reported Common Test group. It excludes `test/unit/` and resets test data before
each group. New integration scenarios should use named Common Test cases in
`*_SUITE.erl` files under `test/`; migrate existing modules incrementally.
Do not run these groups in parallel: registered OTP names, application settings,
and database resets are still shared within each suite.

The runner does not read `.env` or use `DATABASE_URL`. By default it connects to
the maintenance database in `docker-compose.test.yaml` on port 55433. Each
Common Test suite creates a uniquely named `bibleit_test_<random>` database,
applies the application's migrations, then drops that database during teardown.
`bibleit_test_database:reset/0` checks the actual connection's database name
against the fixture's marker before truncating anything. The test PostgreSQL
service uses temporary storage and is separate from the development service.

For another disposable PostgreSQL instance, set `BIBLEIT_TEST_DATABASE_URL` in
the shell. Its maintenance database name must end in `_test`; its user needs
permission to create databases. Use only an instance intended for testing.
Concurrent Rebar3 runs receive different databases. An interrupted VM can leave
an orphan database; `make test-db-down` discards the local test service's data.
Common Test reports and captured EUnit output are under `_build/test/logs/`.
The migration bridge temporarily restores the repository working directory for
legacy relative file assertions; new suites should use `code:priv_dir/1` and
Common Test's per-case `priv_dir` for files.

The next migration stages are:

1. Split broad EUnit scenarios into independent Common Test behaviors, sharing
   explicit fixtures that restore application settings and await process exit.
2. Define the supported client/protocol versions and data upgrade paths. Add
   versioned fixtures from those releases for commands, responses, WebSocket
   events, credentials, translations, and database upgrades. The current restart
   suite covers current-schema recovery and rejection of obsolete Live records;
   it does not establish compatibility with an older released database.
3. Replace frontend source-regex extraction with stable JavaScript module
   boundaries or DOM behavior tests, retaining useful regression assertions.
4. Add pull-request CI for `rebar3 check`, release startup smoke tests, and a
   declared OTP support matrix. Track runtime and coverage gaps before choosing
   gates; add PropEr only for invariants that benefit from generated inputs.

### Rebar3 utilities

```sh
make deps                    # Resolved dependency tree
make rebar3 ARGS="version"  # Rebar3, OTP, and ERTS versions
make rebar3 ARGS="tree"     # Equivalent to make deps
```



Native parser/iterator/cache fuzzing runs with the library's ordinary `test`
target (also part of `make test`). Run `make -C libbibleit test-sanitize` for the
same fixed-seed corpus under AddressSanitizer and UndefinedBehaviorSanitizer.
Native changes belong in the sibling `../libbibleit` repository; commit there
and update this repository's submodule revision after verification.

### Real-browser security runner

The browser suite launches a fresh headless Chrome/Chromium profile over a private
DevTools pipe. It does not reuse your open browser or stored credentials. The
runner detects the standard macOS Chrome path and common Linux executable paths;
set `BIBLEIT_BROWSER_BIN` to an absolute executable path otherwise. It uses no
additional npm packages, blocks external page requests, and removes its temporary
profile after execution. Missing browser dependencies fail the suite explicitly.
For example:

```sh
make test-db-up
make rebar3 ARGS='ct --suite=test/bibleit_browser_security_SUITE.erl'
make test-db-down
```

The suite seeds disposable data, tests escaping/sanitization with CSP bypassed,
then reenables CSP for enforcement and normal-feature checks. CSP eval assertions
also disable DevTools' own eval bypass. See the security results ledger for the
browser version and bounded coverage limitations.


### Security review runtime and packaging boundaries

`make release` and Rebar's release/tar hooks stage only the resources listed in
`config/release-files.txt` and public documents listed in
`priv/config/public-docs.txt`. Update these manifests when adding resources.
Builds verify packaged contents; local development certificates, SSH keys,
`.DS_Store`, assessment records and offline recovery SQL are excluded. The HTTP
raw-document route enforces the public manifest even with a `docs_dir` override.
The release disables inbound distributed Erlang and EPMD startup and supplies no
shared default cookie; remote console/RPC commands are unavailable by default.

Optional integration values may be blank in `.env.example`: blank integrations
are disabled, while partial OAuth, email or Turnstile key pairs fail startup.
PostgreSQL URLs without `sslmode` require CA/hostname-verified TLS. Explicit
`sslmode=disable` is used by the disposable local fixture; explicit `require`
encrypts without authenticating the peer. Unknown modes fail configuration.
Reviewed translation imports use HTTPS eBible URLs and bounded verified TLS;
provider requests enforce the same transport policy and deadline.

New migration journal entries record SQL filenames and SHA-256. Startup rejects
unverified legacy journals or changed SQL. Preserve a backup and establish the
migration lineage before an audited canonical-schema migration or an explicitly
approved disposable development rebuild; never blindly backfill current hashes
into legacy rows. The security campaign does not reset development databases.

Run the focused review with the disposable test service:

```sh
make test-db-up
make rebar3 ARGS='ct --suite test/bibleit_security_review_SUITE.erl,test/bibleit_account_access_SUITE.erl,test/bibleit_import_access_SUITE.erl'
make test
make rebar3 ARGS='as prod tar'
make test-db-down
```
