# Architecture

## Architecture

Bibleit is one Erlang/OTP release with two public transports: HTTP/WebSocket
and native SSH. In production, Fly terminates HTTPS and forwards clear HTTP
inside the private network; SSH remains an end-to-end encrypted TCP service.
In local development, clients connect directly to ports `8080` and `2222`.

```mermaid
flowchart LR
  browser[Browser and Live viewer]
  cli[Bibleit CLI]
  edge[Public edge]

  subgraph release[Bibleit OTP release]
    http[HTTP and WebSocket]
    ssh[SSH daemon]
    domain[Accounts, permissions,<br/>translations and Lives]
  end

  providers[OAuth and email providers]
  postgres[(PostgreSQL)]
  files[(Translation indexes)]
  hostKey[(SSH host key)]

  browser -->|HTTPS / WSS| edge
  cli -->|HTTPS or SSH| edge
  edge --> http
  edge --> ssh
  http --> domain
  ssh --> domain
  http <--> providers
  domain --> postgres
  domain --> files
  ssh --> hostKey
```

The transport boundary is intentional: Cowboy does not connect back through
SSH, and the SSH channel does not make HTTP requests. Both adapters call the
same in-process application/domain modules, so permission checks, account
limits, Live ownership, and translation-library rules are shared.

### HTTP templates

Server-rendered HTML lives in `priv/templates/*.html` and uses bbmustache.
Keep business logic in Erlang and pass structured data to page templates;
use Mustache sections for lists, empty states, and boolean attributes.
Keep one-off panels and rows inside their owning page, supplying nested maps
and lists as bindings instead of rendering chains of fragments. Missing optional
section bindings are false, following standard Mustache semantics. Reserve
partials such as `{{> navigation-group}}` for genuinely shared components;
they resolve from that directory with an `.html` extension. Icons share one
`icon.html` component and the SVG sprite in `priv/static/icons.svg`.
Name templates after their purpose (for example, `ssh-key-row.html` or
`access-token-limit-notice.html`), never numbered extraction fragments.
Use descriptive binding names, such as `display_name`, `expiration_options`,
and `browser_sessions`, rather than positional placeholders. Keep names scoped
to the nested map or list item that supplies their data.

Ordinary `{{value}}` bindings escape HTML. Use them with raw user data, without
calling `html_escape` first. Triple braces are reserved for trusted rendered
HTML or explicitly pre-escaped legacy bindings. The migrated templates retain
triple braces where their existing Erlang callers already escape data. Do not
disable bbmustache's escaping globally.

The renderer injects `i18n` from `bibleit_i18n` and the JSON catalogs in
`priv/i18n`. Store labels as UTF-8 text with proper Unicode characters, without
HTML entities, and use ordinary bindings such as `{{i18n.notifications}}`.
Static `{{i18n:key}}` tokens also escape translated text. Never translate
rendered user content or run a translation pass over a completed page.
Action confirmations and errors use the same catalogs; browser feedback reads
the localized bindings in `action-feedback-i18n.html`.

Parsed templates and partials are cached until restart. After editing them in
a running development VM, call `bibleit_http_template:clear_cache/0`, or set
`application:set_env(bibleit_server, template_cache, false)` for live reload.
The `fragment/2` compatibility entry point only strips the template file's
final newline; new page/component code should use `render/2`.

### Documentation translations

`/docs` (and the legacy `/docs/docs.html` URL) renders
`priv/templates/docs-page.html` through `bibleit_http_docs`. Add documentation
copy to `priv/i18n/docs-en.json`, `docs-pt.json`, and `docs-de.json`. These catalogs
are loaded by `bibleit_i18n` with the same English fallback as the application.
Keep their values as plain UTF-8 text, without HTML entities or markup; normal
Mustache bindings escape them. Translate metadata, image descriptions and
accessibility labels as well as headings and prose. The homepage remains in
`docs/index.html`; CSS and documentation images remain in `docs/`.

Translate complete paragraphs. Where a paragraph contains inline code, keyboard
shortcuts or links, use named placeholders and define their markup in the template:

```html
<p>{{#docs_text}}docs_confirm_install
{{#version_command}}<code>bibleit version</code>{{/version_command}}
{{#config_path}}<code>~/.bibleit/config.json</code>{{/config_path}}
{{/docs_text}}</p>
```

The catalog value can reorder `{{version_command}}` and `{{config_path}}` for each
language. `docs_text` escapes translated prose and inserts only the trusted
template fragments; it never interprets catalog text as HTML or Mustache.
Keep full command examples in the template, untranslated. Tests check identical
catalog keys, matching placeholders, English fallback, escaping and unchanged
command examples across locales.

Language selection follows the existing query → cookie → browser preference
order. The docs language links persist an explicit selection in `bibleit_locale`
and retain the locale in public navigation and sign-in links. After changing
catalogs in a running development server, restart it to refresh the catalog cache.

### Browser and CLI authentication flow

The server implements an OAuth-style authorization-code flow with PKCE and
a loopback callback for the forthcoming CLI. The diagram describes the intended
client integration; the native CLI is still in development. The browser session
never leaves the browser, and the client receives a revocable access token.

```mermaid
sequenceDiagram
  participant CLI as Bibleit CLI
  participant Browser
  participant Server as Bibleit HTTP
  participant DB as PostgreSQL

  CLI->>CLI: Start loopback callback and create PKCE challenge
  CLI->>Browser: Open authorization page
  Browser->>Server: Sign in and approve CLI
  Server->>DB: Store one-use code bound to account and PKCE
  Server-->>Browser: Redirect with one-use code
  Browser-->>CLI: Call localhost callback
  CLI->>Server: Exchange code and verifier
  Server->>DB: Consume code and store hashed access token
  Server-->>CLI: Return access token once
  CLI->>Server: Run commands with bearer token
```

Browser sessions, OAuth state, email action tokens, CLI authorization codes,
and access-token hashes are all persisted in PostgreSQL. Raw bearer tokens,
browser cookies, password-reset secrets, and authorization codes are returned
only where needed and are never stored in plaintext by the server.

### SSH authentication flow

SSH has no application-level login command. OpenSSH proves possession of a
private key during the SSH handshake, and Bibleit accepts it only when the
matching public key was registered through the dashboard first.

```mermaid
sequenceDiagram
  participant CLI as Bibleit CLI
  participant SSH as OpenSSH and Bibleit SSH
  participant DB as PostgreSQL
  participant Domain as Bibleit services

  CLI->>SSH: Connect using ssh config, agent, or --identity
  SSH->>DB: Resolve the offered public-key fingerprint
  alt Registered key
    DB-->>SSH: Return account identity
    SSH->>Domain: Execute as that account
    Domain->>DB: Read or mutate durable account and Live state
    Domain-->>CLI: Return result or event
  else Unknown or revoked key
    DB-->>SSH: Not found
    SSH-->>CLI: Reject authentication
  end
```

The SSH username is fixed to the descriptive value `bibleit-cli` by the native
CLI but is not an identity claim. Identity comes exclusively from the verified
key fingerprint. The SSH host private key identifies the server; user private
keys remain on their devices and never enter Bibleit storage.

### Storage boundaries

| Boundary | Durable contents | Deliberately excluded |
| --- | --- | --- |
| PostgreSQL | Users, provider identities, Argon2 password hashes, accounts, memberships, roles, permissions, plans, quotas, hashed tokens and sessions, SSH public keys, CLI/OAuth/email flows, Lives and Live payloads | Raw access tokens, raw browser cookies, private keys, translation bodies, BEAM process IDs |
| Translation storage | Installed `.bt`, `.bidx`, and `.bsearch` assets read by the NIF | Account ownership and enabled-library choices |
| Secret filesystem/volume | SSH host private key | User SSH private keys and application database records |
| BEAM memory | Active Live subscribers, supervised process state, connection-to-identity bindings, open translation handles | Durable account or plan truth |

PostgreSQL is mandatory and is the source of truth. Translation assets remain
outside it because they are large, immutable, and optimized for native indexed
reads. DETS is not part of the runtime architecture.

## Source layout

The OTP application is grouped by responsibility rather than by transport
implementation detail:

- `src/auth/` owns account records, authorization, email/password accounts,
  and outbound email delivery.
- `src/ssh/` owns the supervised native SSH listener, SSH public-key callback,
  channel shell, and connection-to-account identity registry.
- `src/http/` owns Cowboy listeners, browser sessions, OAuth callbacks, HTTP
  pages, and WebSocket handling.
- `src/live/`, `src/translations/`, and `src/protocol/` contain the respective
  domain state and command handling.
- `libbibleit/` is the pinned native-library submodule; its `config/` assets
  are copied into `priv/config/` during the build.

SSH usernames are intentionally not account identifiers. A registered public
key authenticates its owning account regardless of the username supplied to
`ssh`; an unknown or missing key is rejected. The Bibleit CLI uses the
descriptive transport username `bibleit-cli` by default.

