diff --git a/README.md b/README.md index 8112fff..dde6406 100644 --- a/README.md +++ b/README.md @@ -17,16 +17,17 @@ A Nostr index relay built with Phoenix 1.8 / Elixir. Stores and serves Nostr eve ### Automated setup -Run the setup script — it installs Erlang/Elixir via asdf, starts the Apache AGE database container, and runs `mix setup`: +Run the setup script — on Debian/Ubuntu it installs Erlang/Elixir from **Team RabbitMQ’s apt repositories** ([Elixir install guide](https://elixir-lang.org/install.html)); on Fedora/RHEL it uses **asdf**. It starts the Apache AGE database container and runs `mix setup`: ```bash chmod +x setup.sh ./setup.sh ``` -After setup, database credentials are written to `.env`. Source it before running any `mix` commands: +After setup, database credentials are written to `.env` (including `REQUIRE_DB=true` for integration tests). Source it before running `mix` tasks that need the DB or asdf’s `mix` in a new terminal: ```bash +source "$HOME/.asdf/asdf.sh" # if `mix` is not found source .env ``` @@ -53,6 +54,7 @@ export POSTGRES_PORT=5455 export POSTGRES_USER=postgres export POSTGRES_PASSWORD=postgres export POSTGRES_DB=gc_index_relay_dev +export REQUIRE_DB=true mix setup ``` @@ -60,24 +62,12 @@ mix setup ### Starting the server ```bash +source "$HOME/.asdf/asdf.sh" # if needed source .env mix phx.server ``` -The relay is available at [http://localhost:4000](http://localhost:4000). After edits to `config/config.exs`, `config/dev.exs`, or `config/runtime.exs`, restart the server manually; other code is hot-reloaded. - -## API - -| Method | Path | Description | -|--------|------|-------------| -| `GET` | `/api` | List available endpoints | -| `GET` | `/api/events` | Query events via URL params (`since`, `until`, `limit` required) | -| `POST` | `/api/events/filter` | Query events with a NIP-01 filter body (`limit` required) | -| `GET` | `/api/events/:id` | Fetch a single event by ID | -| `POST` | `/api/events` | Publish a new event | -| `DELETE` | `/api/events/:id` | Delete an event by ID | -| `GET` | `/api/swagger` | Swagger UI | -| `GET` | `/health` | Health check | +The relay listens on port **4000** by default (see `PORT` in `config/runtime.exs`). Open [http://localhost:4000](http://localhost:4000). After edits to `config/config.exs`, `config/dev.exs`, or `config/runtime.exs`, restart the server manually; other code is hot-reloaded. ### NIP-11 relay info @@ -95,13 +85,15 @@ Unit tests (no database required): mix test.unit ``` -Integration tests (requires the database to be running): +Integration tests (requires the database to be running). You must have `REQUIRE_DB=true` (included in `.env` from `setup.sh`) so the Repo starts under test: ```bash source .env mix test.integration ``` +Without `.env`: `REQUIRE_DB=true mix test.integration` + Run the full integration probe against the relay API (covers all endpoints, CORS, NIP-11, NIP-70): ```bash diff --git a/config/runtime.exs b/config/runtime.exs index e597e05..2cf2104 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -23,12 +23,14 @@ end config :gc_index_relay, GcIndexRelayWeb.Endpoint, http: [port: String.to_integer(System.get_env("PORT", "4000"))] +# Defaults match setup.sh / .env.example (Apache AGE on host port 5455). Production +# replaces this with DATABASE_URL in the block below. config :gc_index_relay, GcIndexRelay.Repo, hostname: System.get_env("POSTGRES_HOST") || "localhost", - port: String.to_integer(System.get_env("POSTGRES_PORT") || "5432"), - username: System.get_env("POSTGRES_USER"), - password: System.get_env("POSTGRES_PASSWORD"), - database: System.get_env("POSTGRES_DB") + port: String.to_integer(System.get_env("POSTGRES_PORT") || "5455"), + username: System.get_env("POSTGRES_USER") || "postgres", + password: System.get_env("POSTGRES_PASSWORD") || "postgres", + database: System.get_env("POSTGRES_DB") || "gc_index_relay_dev" if config_env() == :prod do database_url = diff --git a/config/test.exs b/config/test.exs index fd4c917..54bdbe8 100644 --- a/config/test.exs +++ b/config/test.exs @@ -8,9 +8,10 @@ config :gc_index_relay, :start_repo, System.get_env("REQUIRE_DB") == "true" # to provide built-in test partitioning in CI environment. # Run `mix help test` for more information. config :gc_index_relay, GcIndexRelay.Repo, - username: System.get_env("POSTGRES_USER"), - password: System.get_env("POSTGRES_PASSWORD"), - database: "#{System.get_env("POSTGRES_DB")}#{System.get_env("MIX_TEST_PARTITION")}", + username: System.get_env("POSTGRES_USER", "postgres"), + password: System.get_env("POSTGRES_PASSWORD", "postgres"), + database: + "#{System.get_env("POSTGRES_DB", "gc_index_relay_dev")}#{System.get_env("MIX_TEST_PARTITION")}", hostname: System.get_env("POSTGRES_HOST", "localhost"), port: String.to_integer(System.get_env("POSTGRES_PORT", "5455")), show_sensitive_data_on_connection_error: true, diff --git a/setup.sh b/setup.sh index 8b3fa53..b263aea 100755 --- a/setup.sh +++ b/setup.sh @@ -7,6 +7,10 @@ # - Docker must already be installed (https://docs.docker.com/engine/install/) # - sudo access to install OS packages (apt-get on Debian/Ubuntu, dnf/yum on Fedora/RHEL) # +# Debian / Ubuntu: Erlang + Elixir are installed from Team RabbitMQ’s apt repositories, as +# recommended on https://elixir-lang.org/install.html (Launchpad PPA on Ubuntu; Cloudsmith +# erlang debs on Debian amd64). Fedora/RHEL still use asdf-compiled Erlang. +# # Usage: # chmod +x setup.sh # ./setup.sh @@ -51,6 +55,88 @@ require_cmd() { command -v "$1" &>/dev/null || err "'$1' is not installed or not on PATH. $2" } +# Install modern Erlang + Elixir via RabbitMQ-maintained apt repos (Elixir install docs). +# Returns 0 on success, 1 to fall back to asdf. +install_elixir_erlang_via_rabbitmq_apt() { + local id version + [ -f /etc/os-release ] || return 1 + # shellcheck source=/dev/null + . /etc/os-release + id="${ID:-}" + version="${VERSION_CODENAME:-}" + + if [ "$id" = "linuxmint" ]; then + version="${UBUNTU_CODENAME:-$version}" + fi + + case "$id" in + ubuntu | pop | linuxmint) + log "Installing Erlang + Elixir via RabbitMQ Erlang PPA (https://elixir-lang.org/install.html)..." + sudo apt-get install -y software-properties-common + sudo add-apt-repository -y ppa:rabbitmq/rabbitmq-erlang + sudo apt-get update -qq + sudo apt-get install -y git elixir erlang + ;; + debian) + if [ "$(uname -m)" != "x86_64" ]; then + warn "RabbitMQ Cloudsmith Erlang packages are amd64-only; use asdf on this architecture." + return 1 + fi + case "$version" in + bullseye | bookworm | trixie) + log "Installing Erlang via Team RabbitMQ apt + elixir (Debian ${version}; https://www.rabbitmq.com/docs/install-debian)..." + sudo apt-get install -y curl gnupg apt-transport-https + curl -1sLf "https://keys.openpgp.org/vks/v1/by-fingerprint/0A9AF2115F4687BD29803A206B73A36E6026DFCA" | + sudo gpg --dearmor | sudo tee /usr/share/keyrings/com.rabbitmq.team.gpg >/dev/null + sudo tee /etc/apt/sources.list.d/rabbitmq-erlang.list >/dev/null </dev/null; then + warn "elixir not found on PATH after apt install." + return 1 + fi + if ! elixir --version 2>/dev/null | grep -qE 'Elixir 1\.(1[5-9]|[2-9][0-9])'; then + warn "Elixir from apt is below 1.15; falling back to asdf." + return 1 + fi + log "$(elixir --version 2>/dev/null | grep Elixir || true)" + return 0 +} + # --------------------------------------------------------------------------- # 1. Pre-flight checks # --------------------------------------------------------------------------- @@ -111,65 +197,81 @@ else fi # --------------------------------------------------------------------------- -# 3. asdf version manager +# 2b. Erlang + Elixir (apt on Debian/Ubuntu via RabbitMQ repos, else asdf) # --------------------------------------------------------------------------- -if [ ! -d "$ASDF_DIR" ]; then - log "Installing asdf $ASDF_VERSION..." - git clone https://github.com/asdf-vm/asdf.git "$ASDF_DIR" --branch "$ASDF_VERSION" -else - log "asdf already installed at $ASDF_DIR" +ELIXIR_FROM_APT=0 +if command -v apt-get &>/dev/null; then + if install_elixir_erlang_via_rabbitmq_apt; then + ELIXIR_FROM_APT=1 + log "Using apt-installed Erlang/Elixir (no asdf Erlang/Elixir steps)." + else + warn "RabbitMQ apt path skipped or failed — installing Erlang/Elixir with asdf." + fi fi -# Source asdf for this script session -# shellcheck source=/dev/null -source "$ASDF_DIR/asdf.sh" - -# Persist asdf sourcing to the user's shell rc (idempotent) -add_asdf_to_rc() { - local rc="$1" - local line='. "$HOME/.asdf/asdf.sh"' - if [ -f "$rc" ] && ! grep -qF 'asdf/asdf.sh' "$rc"; then - echo "" >> "$rc" - echo "# asdf version manager" >> "$rc" - echo "$line" >> "$rc" - log "Added asdf to $rc (will take effect in new shells)" - fi -} -add_asdf_to_rc "$HOME/.bashrc" -add_asdf_to_rc "$HOME/.zshrc" 2>/dev/null || true +if [ "$ELIXIR_FROM_APT" -eq 0 ]; then + # --------------------------------------------------------------------------- + # 3. asdf version manager + # --------------------------------------------------------------------------- -# --------------------------------------------------------------------------- -# 4. Erlang -# --------------------------------------------------------------------------- + if [ ! -d "$ASDF_DIR" ]; then + log "Installing asdf $ASDF_VERSION..." + git clone https://github.com/asdf-vm/asdf.git "$ASDF_DIR" --branch "$ASDF_VERSION" + else + log "asdf already installed at $ASDF_DIR" + fi -asdf plugin add erlang 2>/dev/null || true + # shellcheck source=/dev/null + source "$ASDF_DIR/asdf.sh" + + add_asdf_to_rc() { + local rc="$1" + local line='. "$HOME/.asdf/asdf.sh"' + if [ -f "$rc" ] && ! grep -qF 'asdf/asdf.sh' "$rc"; then + echo "" >> "$rc" + echo "# asdf version manager" >> "$rc" + echo "$line" >> "$rc" + log "Added asdf to $rc (will take effect in new shells)" + fi + } + add_asdf_to_rc "$HOME/.bashrc" + add_asdf_to_rc "$HOME/.zshrc" 2>/dev/null || true + + # --------------------------------------------------------------------------- + # 4. Erlang + # --------------------------------------------------------------------------- + + asdf plugin add erlang 2>/dev/null || true + + if asdf list erlang 2>/dev/null | grep -qF "$ERLANG_VERSION"; then + log "Erlang $ERLANG_VERSION already installed" + else + log "Installing Erlang $ERLANG_VERSION (compiles from source — takes a few minutes)..." + asdf install erlang "$ERLANG_VERSION" + fi -if asdf list erlang 2>/dev/null | grep -qF "$ERLANG_VERSION"; then - log "Erlang $ERLANG_VERSION already installed" -else - log "Installing Erlang $ERLANG_VERSION (compiles from source — takes a few minutes)..." - asdf install erlang "$ERLANG_VERSION" -fi + asdf global erlang "$ERLANG_VERSION" -asdf global erlang "$ERLANG_VERSION" + # --------------------------------------------------------------------------- + # 5. Elixir + # --------------------------------------------------------------------------- -# --------------------------------------------------------------------------- -# 5. Elixir -# --------------------------------------------------------------------------- + asdf plugin add elixir 2>/dev/null || true -asdf plugin add elixir 2>/dev/null || true + if asdf list elixir 2>/dev/null | grep -qF "$ELIXIR_VERSION"; then + log "Elixir $ELIXIR_VERSION already installed" + else + log "Installing Elixir $ELIXIR_VERSION..." + asdf install elixir "$ELIXIR_VERSION" + fi -if asdf list elixir 2>/dev/null | grep -qF "$ELIXIR_VERSION"; then - log "Elixir $ELIXIR_VERSION already installed" + asdf global elixir "$ELIXIR_VERSION" + log "$(elixir --version | grep 'Elixir')" else - log "Installing Elixir $ELIXIR_VERSION..." - asdf install elixir "$ELIXIR_VERSION" + hash -r fi -asdf global elixir "$ELIXIR_VERSION" -log "$(elixir --version | grep 'Elixir')" - # --------------------------------------------------------------------------- # 6. Apache AGE database (Docker) # --------------------------------------------------------------------------- @@ -210,12 +312,14 @@ export POSTGRES_PORT=$POSTGRES_PORT export POSTGRES_USER=$POSTGRES_USER export POSTGRES_PASSWORD=$POSTGRES_PASSWORD export POSTGRES_DB=$POSTGRES_DB +export REQUIRE_DB=true EOF else log ".env already exists — skipping (delete it to regenerate)" fi -# Export for the current session so mix setup can connect +# Export DB vars for this session so mix setup can connect. Do not export REQUIRE_DB here — +# that would make a follow-up `mix test.unit` in the same shell start the Repo. It is only in `.env` for integration tests. export POSTGRES_HOST POSTGRES_PORT POSTGRES_USER POSTGRES_PASSWORD POSTGRES_DB # --------------------------------------------------------------------------- @@ -224,9 +328,15 @@ export POSTGRES_HOST POSTGRES_PORT POSTGRES_USER POSTGRES_PASSWORD POSTGRES_DB cd "$PROJECT_DIR" -log "Installing Hex and Rebar (if needed)..." -mix local.hex --force --if-missing -mix local.rebar --force --if-missing +if [ "$ELIXIR_FROM_APT" -eq 1 ]; then + require_cmd mix "apt elixir package should provide mix; check PATH includes /usr/bin" +else + require_cmd mix "asdf should provide mix; run: source \"\$HOME/.asdf/asdf.sh\" or open a new terminal" +fi + +log "Installing Hex and Rebar..." +mix local.hex --force +mix local.rebar --force log "Fetching dependencies..." mix deps.get @@ -241,11 +351,27 @@ mix setup echo log "Setup complete!" echo -echo " To start the server:" -echo " source .env" -echo " mix phx.server" -echo -echo " Then open: http://localhost:4000" -echo " REST API: http://localhost:4000/api/events" -echo -warn "Open a new terminal (or run 'source ~/.bashrc') for asdf to work in future sessions." +if [ "$ELIXIR_FROM_APT" -eq 1 ]; then + echo " To start the server (system Elixir from apt):" + echo " cd \"$PROJECT_DIR\" && source .env && mix phx.server" + echo + echo " Then open: http://localhost:4000" + echo " REST API: http://localhost:4000/api/events" + echo + echo " Integration tests:" + echo " source .env && mix test.integration" + echo + warn "If mix is not found in a new terminal, ensure /usr/bin is on your PATH." +else + echo " To start the server (use asdf’s mix — see warning below if mix is missing):" + echo " source \"\$HOME/.asdf/asdf.sh\" # once per shell, if needed" + echo " cd \"$PROJECT_DIR\" && source .env && mix phx.server" + echo + echo " Then open: http://localhost:4000" + echo " REST API: http://localhost:4000/api/events" + echo + echo " Integration tests: same shell with .env (includes REQUIRE_DB=true):" + echo " source .env && mix test.integration" + echo + warn "Open a new terminal (or run 'source ~/.bashrc') so asdf provides mix/elixir in future sessions." +fi