643 lines
28 KiB
Markdown
643 lines
28 KiB
Markdown
# Who Need Help
|
|
|
|
Who Need Help is a working mutual-aid MVP for urgent, local, voluntary help.
|
|
The first priority is pickup and delivery of a legal medicine that has already
|
|
been purchased or reserved. The same urgent-help flow also supports safe,
|
|
non-emergency fuel delivery, wheel help, bicycle and motorcycle problems,
|
|
vehicle breakdowns, and practical support after a secured road incident.
|
|
|
|
It is not an emergency or medical service, does not prescribe or sell medicine,
|
|
and does not process payments. A helper may publish an optional external
|
|
thank-you link; money goes directly between users outside the platform.
|
|
|
|
## What is implemented
|
|
|
|
- Phoenix 1.8 LiveView application with passwordless email magic links,
|
|
optional passwords, optional Google OpenID Connect registration/sign-in, and
|
|
18+ self-attestation.
|
|
- Data-driven, translated category tree with validated per-category fields,
|
|
seven selectable urgent/roadside scenarios, community proposals/votes, and
|
|
human approve/reject/merge tools.
|
|
- Separate social Activity mode for coffee, cinema, walks, and hikes, with
|
|
organizer-approved membership, capacity-safe joins, private group chat, and
|
|
exact coordinates visible only to approved participants. Activities never
|
|
affect urgent-helper reputation; Activity and Activity-message reports expose
|
|
only the linked evidence to audited moderators.
|
|
- Request lifecycle: `open → matched → in_progress → completed`, plus cancel
|
|
and expiry paths.
|
|
- PostgreSQL/PostGIS locations, MapLibre map, private matched chat, Phoenix
|
|
PubSub/Presence, and optional consent-driven live location sharing.
|
|
- Handover code plus both-party confirmation before verified completion.
|
|
- Double-blind reviews, public trust summaries, and a helper leaderboard that
|
|
prioritizes unique location-supported and handover-verified counterparts
|
|
before raw totals.
|
|
- Bidirectional discovery blocks, scoped reports, account/request/category
|
|
moderation, abuse-signal review, and audited moderator access to only the
|
|
conversation linked by a report.
|
|
- Separate public support and content-removal intake, including moderation
|
|
appeals, account deletion/data requests, a URL-only TAKE IT DOWN form,
|
|
verified-contact status links, operator alerts, and audited staff queues.
|
|
- Optional GPS evidence derived from browser accuracy envelopes. Raw current
|
|
positions are deleted on stop, terminal match state, or participant block.
|
|
- PostgreSQL-backed cross-replica action-limit policies configured by the
|
|
operator. No unapproved numeric thresholds are enabled by default.
|
|
- Public manual social links, always marked unverified, plus optional verified
|
|
GitHub linking through a state- and PKCE-protected OAuth flow. Provider access
|
|
tokens are not stored.
|
|
- EN/UK/RU UI foundation and installable PWA metadata/service worker.
|
|
- Native Android WebView client with the same authenticated LiveView, map,
|
|
private chat, and a user-started location foreground service. Its persistent
|
|
notification exposes Stop, it continues while the Activity is minimized, and
|
|
it retains only the current point. Reproducible Docker targets export
|
|
distinct local and public-staging debug APKs; production signing and store
|
|
publication are not configured.
|
|
- Local, advisory Codex category review through the user's ChatGPT-authenticated
|
|
Codex CLI. It receives a PII-free export and never writes to the database.
|
|
- One immutable release image with `web`, `worker`, combined `app`, and
|
|
one-shot `migrate` roles.
|
|
- Docker Compose and Helm/kind deployment paths with 2 web and 2 worker
|
|
replicas by default.
|
|
|
|
Additional social providers, background PWA or unattended location tracking,
|
|
platform payments, production Android signing/store publication, iOS,
|
|
automatic punitive fraud decisions, and jurisdiction-specific public-launch
|
|
policies are deliberately not claimed as complete.
|
|
|
|
## Fast start with Docker Compose
|
|
|
|
The public single-server path uses the compact application topology plus a
|
|
separately managed Caddy edge. Production and test can run as isolated
|
|
Compose projects with distinct PostGIS volumes and secrets while sharing only a
|
|
Docker network used for HTTPS reverse proxying. See the
|
|
[operations runbook](docs/operations.md#two-independent-checkouts-and-one-env-in-each)
|
|
for the verified order of operations. Redis is not a project dependency.
|
|
|
|
Prerequisite: Docker with the Compose plugin.
|
|
|
|
```bash
|
|
./scripts/deploy-up.sh .env
|
|
```
|
|
|
|
Open:
|
|
|
|
- app: <http://localhost:4010>
|
|
- local email inbox: <http://localhost:8027>
|
|
|
|
Compose starts Traefik, PostGIS, Mailpit, a migration runner, 2 web replicas,
|
|
and 2 Oban worker replicas. It waits for readiness and verifies a PubSub message
|
|
broadcast from a different BEAM node. Registration emails appear in Mailpit.
|
|
|
|
Four deployment settings in the ignored environment select the runtime without
|
|
editing Compose files:
|
|
|
|
```dotenv
|
|
DEPLOYMENT_TARGET=compose # compose or kubernetes
|
|
APP_TOPOLOGY=split # split or compact
|
|
DATABASE_MODE=container # container or external
|
|
DATABASE_URL=ecto://... # the selected database
|
|
```
|
|
|
|
`split` keeps independently scalable web and worker containers and uses
|
|
Traefik; `WEB_REPLICAS` and `WORKER_REPLICAS` default to 2. `compact` starts one
|
|
Phoenix+Oban container and exposes it directly to the host reverse proxy. It is
|
|
the lower-overhead first-server mode. `container` starts the project-owned
|
|
PostGIS service. `external` excludes that service from the active Compose
|
|
model, checks PostgreSQL/PostGIS connectivity, applies migrations, and only
|
|
then starts the application. There is no Redis service: Oban, rate limits, and
|
|
cross-instance coordination use PostgreSQL.
|
|
|
|
Inspect the exact state:
|
|
|
|
```bash
|
|
docker compose -p who_need_help ps -a
|
|
docker compose -p who_need_help logs -f web worker
|
|
./scripts/verify-realtime-cluster.sh compose
|
|
./scripts/verify-beam-runtime.sh compose
|
|
```
|
|
|
|
`ERLANG_PORT_LIMIT` defaults to the normal OTP port limit of `65536`. Keeping
|
|
it explicit prevents a container runtime's unusually large `nofile` limit from
|
|
making every BEAM instance preallocate a multi-gigabyte port table. The runtime
|
|
check above reads every running web/worker VM and fails if its effective limit
|
|
differs from the configured value. Increase it only from measured concurrent
|
|
file, socket, and driver requirements.
|
|
|
|
Create and restore-test a database backup without restoring over the source:
|
|
|
|
```bash
|
|
backup_output=$(./scripts/backup-compose.sh)
|
|
backup_path=$(printf '%s\n' "$backup_output" | sed -n 's/^Backup: //p')
|
|
./scripts/restore-drill-compose.sh "$backup_path"
|
|
```
|
|
|
|
Rehearse the complete current release against that exact backup without
|
|
changing the source Compose project:
|
|
|
|
```bash
|
|
./scripts/upgrade-rehearsal-compose.sh "$backup_path"
|
|
```
|
|
|
|
The rehearsal uses a uniquely named Compose project and database, applies the
|
|
current migrations, starts 2 web and 2 worker replicas, checks the configured
|
|
public-origin proxy behavior and cross-node PubSub, compares application-table
|
|
counts, and removes its containers, networks, volume, and one-run image.
|
|
|
|
Verify that the tracked Git revision can deploy from a clean directory with a
|
|
new environment, independent generated secrets, dynamic ports, and no
|
|
pre-existing image or volume:
|
|
|
|
```bash
|
|
./scripts/clean-deploy-verify.sh
|
|
```
|
|
|
|
The drill starts an isolated 2-web/2-worker Compose project, verifies current
|
|
and repeated migrations, HTTP and Mailpit routing, the four-node BEAM cluster,
|
|
and cross-replica PubSub, then removes its exact project, volumes, image, and
|
|
temporary tracked-file archive. It does not read or change the ordinary
|
|
Compose `.env` or database.
|
|
|
|
For the reproducible encrypted S3-compatible drill, first start the isolated
|
|
load project and then run:
|
|
|
|
```bash
|
|
./scripts/load-stack-up.sh
|
|
./scripts/backup-s3-drill.sh local-encrypted-backup
|
|
```
|
|
|
|
The command generates MinIO and Restic secrets only in ignored mode-`0600`
|
|
`.env.load`, streams `pg_dump` directly into an encrypted Restic repository,
|
|
restores it into a new temporary database, checks corruption and interruption
|
|
failure paths, removes those temporary buckets, and retains the successful
|
|
encrypted bucket in local MinIO. It never writes a plaintext dump to the host.
|
|
|
|
Exercise the real GitHub OAuth, Google OIDC, SMTP protocol clients, and the provider-neutral push
|
|
boundary entirely inside an isolated Docker network:
|
|
|
|
```bash
|
|
./scripts/external-boundaries-run.sh local-boundaries
|
|
```
|
|
|
|
The command publishes no host ports, generates independent one-run credentials
|
|
and a separate metrics bearer token in an ignored mode-`0600` file, verifies
|
|
success, rejection, retry, replay, and timeout paths, retains only non-secret
|
|
evidence, and removes its exact Compose project, database volume, and images.
|
|
It also runs two Oban worker replicas and verifies that request acceptance and
|
|
new-chat events are delivered from their real domain transactions, including
|
|
an Oban retry and pre-transport replay deduplication. The adapter is not
|
|
presented as an FCM or APNs implementation; an external provider must resolve
|
|
the stable user recipient to registered devices.
|
|
|
|
The commands, boundaries, and unclaimed production properties are documented
|
|
in [the operations runbook](docs/operations.md).
|
|
|
|
Local defaults are intentionally limited to local development. Copy
|
|
`.env.example` to `.env` and replace every secret before any public deployment.
|
|
Generate independent values with `mix phx.gen.secret`; do not reuse the session
|
|
secret as the BEAM cookie or handover secret. External Helm deployments must
|
|
set `app.host`, `app.scheme`, and `app.urlPort` to the public URL used in email
|
|
links, and must set `app.mapTileUrl` to a tile service whose policy and capacity
|
|
fit the deployment.
|
|
|
|
For a first Compose deployment, generate an ignored environment on the target
|
|
Docker host. The default is `APP_TOPOLOGY=compact` with a project-owned
|
|
PostGIS container. The command derives that host's Docker socket group,
|
|
generates independent database, Phoenix, handover, cluster, and metrics
|
|
secrets without printing them, writes mode `0600`, and refuses to replace an
|
|
existing file:
|
|
|
|
```bash
|
|
./scripts/init-production-env.sh whoneedhelp.com
|
|
```
|
|
|
|
Configure the verified reverse-proxy source IP/CIDR and transactional email
|
|
provider in that file. `EMAIL_DELIVERY_PROVIDER=smtp` uses the standard Swoosh
|
|
SMTP adapter. `EMAIL_DELIVERY_PROVIDER=unisender_go` uses UniSender Go's HTTPS
|
|
Web API and requires `UNISENDER_GO_API_KEY`; this mode does not need outbound
|
|
SMTP ports. Email registration and magic-link login are unusable for real
|
|
recipients until the selected provider and its accepted sender are configured. Set
|
|
the optional `SUPPORT_INBOX_ADDRESS` to a monitored mailbox to receive
|
|
metadata-only new-case alerts and make replies return to the support team; the
|
|
database queues continue to work when it is empty. See
|
|
[the support and content-removal runbook](docs/support-and-content-removal.md).
|
|
Then validate the file structure and the production Compose render:
|
|
|
|
```bash
|
|
./scripts/validate-production-env.sh .env whoneedhelp.com
|
|
./scripts/deploy-up.sh .env
|
|
```
|
|
|
|
To generate a split deployment against an already provisioned PostgreSQL 18 +
|
|
PostGIS database, supply the mode and URL to the initializer:
|
|
|
|
```bash
|
|
PRODUCTION_APP_TOPOLOGY=split \
|
|
PRODUCTION_DATABASE_MODE=external \
|
|
PRODUCTION_DATABASE_URL='ecto://USER:PASSWORD@DB_HOST/DB_NAME?ssl=true' \
|
|
./scripts/init-production-env.sh whoneedhelp.com
|
|
```
|
|
|
|
Use `ssl=true` when the database provider requires TLS; the database check
|
|
reports whether the observed connection uses TLS without printing the URL or
|
|
credentials. Provider-specific CA/network requirements still have to be
|
|
configured from that provider's verified documentation.
|
|
|
|
For PostgreSQL installed on the same Linux host, keep its TCP listener private
|
|
and connect through its Unix socket. The root-only bootstrap refuses existing
|
|
project roles/databases, backs up `pg_hba.conf`, adds two exact SCRAM rules,
|
|
creates independent production/test roles and empty databases, preloads
|
|
`citext` and PostGIS, verifies both logins, and writes mode-`0600` initializer
|
|
fragments without printing their passwords:
|
|
|
|
```bash
|
|
sudo ./scripts/provision-host-postgres.sh "$USER"
|
|
set -a
|
|
. "$HOME/.config/who_need_help/database-production.env"
|
|
set +a
|
|
./scripts/init-production-env.sh whoneedhelp.com
|
|
unset PRODUCTION_DATABASE_MODE PRODUCTION_DATABASE_URL \
|
|
PRODUCTION_DATABASE_SOCKET_DIR
|
|
```
|
|
|
|
Use `database-test.env` for the isolated test environment. Compose mounts
|
|
only the configured socket directory read-only; Ecto migrations remain the
|
|
source of application schema. Inspect the exact host PostgreSQL state and the
|
|
script's documented impact before the sudo invocation.
|
|
|
|
`compose.production.yaml` leaves local Mailpit stopped. Validation deliberately
|
|
fails while the relay still points to Mailpit or a template marker remains.
|
|
It does not claim to test DNS, certificates, actual mail delivery, the
|
|
deployment's observed proxy source address, or capacity; verify those on the
|
|
target host before opening registration.
|
|
|
|
Rotate all local application secrets and the existing local PostgreSQL role
|
|
without printing the generated values:
|
|
|
|
```bash
|
|
./scripts/rotate-local-secrets.sh
|
|
docker compose up -d --wait
|
|
```
|
|
|
|
This preserves the named PostgreSQL volume, but invalidates existing browser
|
|
sessions and changes handover codes for active local requests.
|
|
|
|
To rotate only the local PostgreSQL role and matching `DATABASE_URL`, without
|
|
changing Phoenix sessions or handover codes, use:
|
|
|
|
```bash
|
|
./scripts/rotate-local-secrets.sh --database-only
|
|
docker compose up -d --wait
|
|
```
|
|
|
|
### Optional Google registration and sign-in
|
|
|
|
Create a Google OAuth 2.0 Web application only for an owned HTTPS origin. Add
|
|
this exact authorized redirect URI in Google Cloud:
|
|
|
|
```text
|
|
https://YOUR_PHX_HOST/auth/google/callback
|
|
```
|
|
|
|
Set both `GOOGLE_OAUTH_CLIENT_ID` and `GOOGLE_OAUTH_CLIENT_SECRET` in the
|
|
ignored `.env` or deployment Secret, then recreate the application containers.
|
|
If both values are empty, the Google buttons remain visible but disabled with
|
|
an explanation. A partial pair is rejected at startup and by the production
|
|
environment validator.
|
|
|
|
The flow requests `openid email profile`, verifies the provider email claim,
|
|
uses state, nonce, and PKCE, and discards provider tokens. Google registration
|
|
creates a confirmed local account only after the user accepts the 18+ safety
|
|
terms. Google login works only for an identity already linked to that local
|
|
account. An existing local account is never merged merely because Google
|
|
returns the same email; sign in by email or password and connect Google from
|
|
the sudo-protected account settings page instead. Leave
|
|
`GOOGLE_OAUTH_BASE_URL` and the Google HTTP timeout variables empty outside the
|
|
isolated protocol drill.
|
|
|
|
### Optional verified GitHub linking
|
|
|
|
Create a GitHub OAuth App with this exact callback URL for the active public
|
|
origin:
|
|
|
|
```text
|
|
https://YOUR_PHX_HOST/auth/social/github/callback
|
|
```
|
|
|
|
Set both `GITHUB_OAUTH_CLIENT_ID` and `GITHUB_OAUTH_CLIENT_SECRET` in the
|
|
ignored `.env` or deployment Secret, then recreate the application containers.
|
|
If both values are empty, the feature stays disabled and the profile explains
|
|
that state. A partial pair is rejected at startup. The flow requests only the
|
|
public GitHub identity, protects callbacks with state and PKCE, binds the flow
|
|
to the initiating signed-in user, and never persists the provider access token.
|
|
The optional `GITHUB_OAUTH_*_URL` and HTTP timeout variables exist only so the
|
|
isolated boundary drill can use its internal mock. Leave them empty for the
|
|
official GitHub endpoints and Req defaults.
|
|
|
|
## Tests
|
|
|
|
The reproducible test command builds a dedicated test target and uses the
|
|
project's PostGIS service:
|
|
|
|
```bash
|
|
./scripts/test.sh
|
|
```
|
|
|
|
It creates/updates only the project-scoped `who_need_help_test` database.
|
|
|
|
The complete local CI-equivalent command uses pinned containerized tools and
|
|
an isolated PostgreSQL volume:
|
|
|
|
```bash
|
|
./scripts/quality.sh
|
|
```
|
|
|
|
It checks shell scripts, Dockerfiles, the GitHub Actions workflow, every Compose
|
|
profile, the rendered Helm chart, tracked-source secrets and infrastructure
|
|
misconfigurations, Elixir formatting/compilation/xref/Credo/Sobelow/Dialyzer,
|
|
retired Hex packages, locked npm dependencies, all Phoenix tests, the pinned
|
|
backup-tool, MinIO server/client, external-boundary mock, and production
|
|
release images. Its generated database credentials are random and exist only
|
|
for that run. The exact database volume, networks, temporary source snapshot,
|
|
and one-run images are removed automatically.
|
|
|
|
For a final or reviewable local run, use the recorded wrapper from a clean
|
|
Git worktree:
|
|
|
|
```bash
|
|
./scripts/quality-record.sh final-audit
|
|
```
|
|
|
|
It runs the same isolated gate and writes `quality.log` plus `summary.txt`
|
|
under `output/regression/final-audit/`. The run directory is private to the
|
|
current user, the files are mode `0600`, and the summary records the exact Git
|
|
commit/tree, tool versions, exit codes, test result, and log SHA-256. The
|
|
generated `output/` tree remains excluded from Git because audit logs can
|
|
contain environment-specific diagnostic details.
|
|
|
|
The same external-service protocol drill used by CI can be run independently:
|
|
|
|
```bash
|
|
./scripts/external-boundaries-run.sh local-boundaries
|
|
```
|
|
|
|
Run the isolated HTTP/WebSocket/authenticated chat/tracking load profile with
|
|
resource, PostgreSQL statement, database-connection, and Ecto pool-wait
|
|
measurements:
|
|
|
|
```bash
|
|
./scripts/load-stack-up.sh
|
|
./scripts/load-run.sh local-load
|
|
./scripts/load-stack-stop.sh
|
|
```
|
|
|
|
The profile has its own generated mode-`0600` environment, Compose project,
|
|
and PostgreSQL volume. It does not use or mutate the ordinary public Compose
|
|
database. Exact inputs and threshold-free evidence are retained below
|
|
`output/performance/local-load/`; see
|
|
[Performance measurement](docs/performance.md) for scope and interpretation.
|
|
|
|
The cursor-pagination database benchmark also creates a one-run Compose
|
|
project, random database credentials, and a separate PostgreSQL volume:
|
|
|
|
```bash
|
|
./scripts/db-scale-benchmark.sh
|
|
```
|
|
|
|
It seeds 50,000 rows in each large benchmark table by default, captures
|
|
PostgreSQL 18 JSON `EXPLAIN (ANALYZE, BUFFERS)` plans before and after the
|
|
cursor-index migration, verifies two consecutive keyset pages for gaps and
|
|
duplicates, and removes its database project and volume. `DB_SCALE_ROWS`
|
|
changes the sample size; it is an experiment input, not a resource minimum.
|
|
Ignored evidence is written below `output/db-scale/`.
|
|
|
|
The browser E2E command creates a uniquely named, isolated Compose project with
|
|
its own PostGIS volume, Mailpit instance, Traefik proxy, two web replicas, and
|
|
two worker replicas:
|
|
|
|
```bash
|
|
./scripts/e2e-run.sh
|
|
```
|
|
|
|
On its first run it generates `.env.e2e` with independent random local secrets
|
|
and mode `0600`. Browser traffic uses the isolated Traefik HTTPS entrypoint;
|
|
Playwright accepts only that one-run proxy's generated certificate. E2E-only
|
|
fixture and failure-injection routes are enabled by a compile-time flag that is
|
|
disabled in the ordinary production image. The suite registers users through
|
|
real Mailpit messages and uses the audited one-time bootstrap command inside
|
|
only the isolated E2E database to create its moderator. It covers
|
|
public/authentication boundaries;
|
|
the urgent medicine flow through matching, realtime chat, handover, and
|
|
double-blind reviews; and the Activity flow through join approval, private
|
|
group chat, message-scoped reporting, blocking, privacy defaults, an unverified
|
|
social link, category moderation, report resolution, and account restriction.
|
|
The same gate checks keyboard skip navigation, WCAG violations and contrast on
|
|
four public pages in both themes, horizontal overflow at three viewport widths,
|
|
an actual locally served raster map tile, LiveView offline/reconnect UI, public
|
|
RU/UK locale persistence, and the selected Ukrainian locale in an authenticated
|
|
LiveView.
|
|
The active failover scenario grants real browser geolocation permission,
|
|
starts consent-based tracking and chat, halts the exact BEAM node serving the
|
|
helper, proves a disconnect and a different runtime boot identity, then checks
|
|
tracking/chat recovery and raw-position deletion in Chromium, Firefox, and
|
|
WebKit.
|
|
An optional list of Playwright spec paths can be passed after the command for a
|
|
focused diagnostic run.
|
|
It retains traces, screenshots, video, and Compose logs under the ignored
|
|
`output/e2e/` directory on failure. The exact E2E project, database volume, and
|
|
networks are removed automatically; the normal `who_need_help` Compose project
|
|
is not recreated.
|
|
|
|
The Android device suite builds dedicated debug and instrumentation APKs,
|
|
boots a fresh emulator in an isolated container without external networking,
|
|
and serves its HTTP fixture only on device loopback. Run the default API 37
|
|
probe or the complete minimum/current API 24/30/34/37 matrix:
|
|
|
|
```bash
|
|
./scripts/android-instrumentation-test.sh
|
|
./scripts/android-matrix-test.sh
|
|
```
|
|
|
|
On first run it generates the ignored `.env.android-test` with a randomized
|
|
device-loopback origin and mode `0600`. The suite covers denied and granted
|
|
location permission, same-origin deep links, Activity recreation, foreground
|
|
location upload while the Activity is backgrounded and destroyed, the
|
|
persistent notification Stop action, a disconnected Stop request with visible
|
|
retry state, and externally forced process death for the non-sticky service.
|
|
Results and failure diagnostics are retained by API under ignored
|
|
`output/android-instrumentation/`; the exact emulator container and one-run
|
|
image are removed automatically.
|
|
|
|
The public-staging variant and its instrumentation APK use the explicit HTTPS
|
|
origin from the ignored `.env`. The smoke probe checks rendered WebView DOM on
|
|
the home and Safety routes. The cross-client probe uses run-scoped users and a
|
|
matched medicine request to verify Android login, private chat in both
|
|
directions, foreground location sharing, browser marker appearance and
|
|
removal, and exact database cleanup:
|
|
|
|
```bash
|
|
./scripts/android-staging-build.sh
|
|
./scripts/android-staging-smoke.sh
|
|
./scripts/android-browser-staging-e2e.sh
|
|
./scripts/android-browser-staging-e2e.sh --remote \
|
|
SSH_HOST:/absolute/path/to/test/checkout
|
|
```
|
|
|
|
The cross-client script refuses an unexpected database, uses a unique fixture
|
|
prefix and manifest, and compares counts across 19 application tables before
|
|
and after cleanup. Remote mode accepts only a container-backed deployment with
|
|
`DEPLOYMENT_ENV=test` and a Compose project ending in `_test`; it keeps the
|
|
Android emulator and browser local, reaches the remote Mailpit API through a
|
|
run-scoped loopback SSH tunnel, and executes fixture preparation, verification,
|
|
and exact cleanup next to the remote test database. It does not delete
|
|
unrelated records.
|
|
|
|
## First administrator
|
|
|
|
Register and confirm the first account, then explicitly bootstrap it:
|
|
|
|
```bash
|
|
./scripts/bootstrap-admin.sh you@example.com --confirm
|
|
```
|
|
|
|
This succeeds only while no administrator exists and writes an audit event.
|
|
After bootstrap, an administrator can manage roles in `/moderation`; the last
|
|
administrator cannot demote themselves. For kind, append `kind`:
|
|
|
|
```bash
|
|
./scripts/bootstrap-admin.sh you@example.com --confirm kind
|
|
```
|
|
|
|
## Optional shared action limits
|
|
|
|
`RATE_LIMIT_POLICIES_JSON` configures atomic PostgreSQL counters shared by every
|
|
web replica. Its shape is:
|
|
|
|
```json
|
|
{"action_name":{"limit":"POSITIVE_INTEGER","window_seconds":"POSITIVE_INTEGER"}}
|
|
```
|
|
|
|
The strings above describe the required types and are not a runnable policy.
|
|
Keep `{}` until numeric limits have been approved from policy and observed
|
|
traffic. Supported actions are listed in `docs/trust-safety.md`.
|
|
|
|
## Local Kubernetes verification
|
|
|
|
The kind script downloads checksum-verified kubectl, Helm, and kind binaries
|
|
into `.tools/`, creates a project-owned cluster, loads the local image, and
|
|
installs the Helm chart, waits for both Deployments, and verifies cross-node
|
|
PubSub and each BEAM VM's effective port limit:
|
|
|
|
```bash
|
|
./scripts/kind-up.sh
|
|
```
|
|
|
|
Pause the local cluster after Kubernetes verification without deleting its
|
|
container, PostGIS volume, Secret, or other cluster state:
|
|
|
|
```bash
|
|
./scripts/kind-stop.sh
|
|
```
|
|
|
|
`./scripts/kind-up.sh` resumes a stopped project-owned control plane before
|
|
loading images and reconciling the chart.
|
|
|
|
Open:
|
|
|
|
- app: <http://localhost:4011>
|
|
- Mailpit: <http://localhost:8028>
|
|
|
|
The script refuses to modify a pre-existing cluster named `who-need-help`
|
|
unless the project ownership marker exists. On first install it creates
|
|
independent random application and PostgreSQL credentials in the
|
|
`who-need-help-local` Kubernetes Secret without writing them to Git. The local
|
|
PostGIS data directory persists inside the kind node. When migrating an older
|
|
project-owned cluster from the former `emptyDir` deployment, the script creates
|
|
and validates a local dump before replacing the database workload, then restores
|
|
that dump. After a successful rollout it also removes the obsolete chart Secret
|
|
and only the local Helm history revisions that stored the former inline
|
|
credential fields.
|
|
|
|
Exercise the verified local rolling-update path without recreating PostGIS or
|
|
the Secret:
|
|
|
|
```bash
|
|
./scripts/kind-rolling-verify.sh local-kind-rollout
|
|
```
|
|
|
|
Compose crash/replacement and Oban retry checks use the separate load project:
|
|
|
|
```bash
|
|
./scripts/load-stack-up.sh
|
|
./scripts/load-resilience-run.sh local-resilience
|
|
```
|
|
|
|
Both scripts retain ignored evidence under `output/resilience/`; their exact
|
|
mutation and cleanup boundaries are documented in the operations runbook.
|
|
|
|
Start and verify the local monitoring profile against every isolated load web
|
|
replica:
|
|
|
|
```bash
|
|
./scripts/observability-run.sh local-observability
|
|
```
|
|
|
|
The command validates Prometheus and Alertmanager configuration, provisions the
|
|
Grafana datasource and dashboard, discovers each current web container as a
|
|
separate target, and exercises a firing/resolved alert by stopping and
|
|
recovering exactly one verified load replica. It prints the loopback-only
|
|
random ports and retains non-secret evidence below `output/observability/`.
|
|
The generated Grafana password remains only in mode-`0600` `.env.load`.
|
|
|
|
Stop only the monitoring services while leaving their local metric volumes and
|
|
the load application running:
|
|
|
|
```bash
|
|
./scripts/observability-stop.sh
|
|
```
|
|
|
|
For an external cluster, provide a real PostgreSQL/PostGIS service and a
|
|
pre-created Secret through required `existingSecret`; the chart never renders
|
|
credentials from tracked values. The Secret must contain `DATABASE_URL`,
|
|
`SECRET_KEY_BASE`, `HANDOVER_SECRET`, `RELEASE_COOKIE`, and `METRICS_TOKEN`, and
|
|
may additionally contain the OAuth credentials described above. Set
|
|
`app.emailDeliveryProvider`; UniSender Go API mode also requires
|
|
`UNISENDER_GO_API_KEY` in that Secret, while SMTP credentials remain Secret
|
|
values in SMTP mode. The chart intentionally has no invented CPU/RAM limits or HPA
|
|
thresholds; measure this application in the target environment before setting
|
|
them.
|
|
|
|
## Local Codex category review
|
|
|
|
Export the redacted proposals from a running release:
|
|
|
|
```bash
|
|
docker compose -p who_need_help exec -T web \
|
|
/app/bin/who_need_help eval \
|
|
'WhoNeedHelp.CatalogModeration.export_open_proposals() |> IO.puts()' \
|
|
> proposals.json
|
|
```
|
|
|
|
Then run the advisory review:
|
|
|
|
```bash
|
|
./scripts/codex-review-categories.sh proposals.json recommendations.json
|
|
```
|
|
|
|
The script exits unless `codex login status` is exactly
|
|
`Logged in using ChatGPT`. It uses no OpenAI API key, no usage-based API billing,
|
|
and no fallback provider. Recommendations require a human moderator action.
|
|
|
|
## Design documentation
|
|
|
|
- [Product specification](docs/product-spec.md)
|
|
- [Architecture](docs/architecture.md)
|
|
- [Trust and safety](docs/trust-safety.md)
|
|
- [Operations runbook](docs/operations.md)
|
|
- [Performance measurement](docs/performance.md)
|
|
- [Implementation verification and known limits](docs/verification.md)
|
|
- [Verified dependency baseline](docs/dependency-baseline.md)
|
|
- [PostgreSQL/PostGIS ADR](docs/decisions/0001-postgresql-postgis-over-spacetimedb.md)
|
|
|
|
Exact dependency versions are locked in `mix.lock`,
|
|
`assets/package-lock.json`, the Dockerfile, Compose file, and tool bootstrap
|
|
script.
|