Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ jobs:
- name: Check out the repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Erlang and Elixir for staging scripts
uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1.24.1
with:
version-file: .tool-versions
version-type: strict

- name: Set up Node.js for the packaged browser pilot
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
Expand All @@ -32,7 +38,7 @@ jobs:
run: docker build --build-arg REVISION="$GITHUB_SHA" -t synixir:ci .

- name: Rehearse HTTPS, native libraries, replacement and database recovery
run: python3 scripts/staging.py rehearse --image synixir:ci --output /tmp/synixir-release-pilot.json
run: elixir scripts/staging.exs rehearse --image synixir:ci --output /tmp/synixir-release-pilot.json

test:
name: Format, compile, and test
Expand Down Expand Up @@ -78,6 +84,9 @@ jobs:
- name: Run tests
run: mix test

- name: Test standalone operations scripts
run: elixir scripts/operations_test.exs

- name: Set up Node.js for the browser example
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
Expand All @@ -104,10 +113,10 @@ jobs:
run: npm test

- name: Verify backup restoration and concurrent limits in isolated databases
run: python3 scripts/operations/database.py --docker-container '${{ job.services.postgres.id }}' drill --output /tmp/synixir-restore.json
run: elixir scripts/operations/database.exs --docker-container '${{ job.services.postgres.id }}' drill --output /tmp/synixir-restore.json

- name: Exercise SDK load, reconnects, large transfers and persisted replay
run: python3 scripts/operations/database.py --docker-container '${{ job.services.postgres.id }}' load --output /tmp/synixir-load.json
run: elixir scripts/operations/database.exs --docker-container '${{ job.services.postgres.id }}' load --output /tmp/synixir-load.json

- name: Validate Prometheus configuration, alert behavior and actual metrics
env:
Expand Down
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,24 @@ require separate setup.

## Try local Docker staging

Start Docker or OrbStack. With Docker Compose v2, Python 3.11 or newer, and
OpenSSL installed, run these commands from the repository for the first setup:
Start Docker or OrbStack. Install Docker Compose v2, OpenSSL, and the Elixir and
Erlang versions in `.tool-versions`. The standalone scripts require Elixir 1.18+
and Erlang/OTP 27+ with no Mix dependencies. Run these commands from the repository
for the first setup:

```sh
python3 scripts/staging.py init
python3 scripts/staging.py build
python3 scripts/staging.py up
elixir scripts/staging.exs init
elixir scripts/staging.exs build
elixir scripts/staging.exs up
```

Open [https://localhost:8443](https://localhost:8443), accept the local self-signed
certificate, and create an account and room. The release includes all browser
examples and uses its own PostgreSQL volume and private Prometheus collector.
Docker builds Elixir, Erlang, and the frontend inside the image.

For an existing installation, run `python3 scripts/staging.py up` to start it.
Use `python3 scripts/staging.py stop` to stop it while retaining the database.
For an existing installation, run `elixir scripts/staging.exs up` to start it.
Use `elixir scripts/staging.exs stop` to stop it while retaining the database.
Initialization runs only once; keep the private `.local/staging` configuration
with its database volume. The [staging runbook](docs/deployment.md) covers the
browser pilot, certificate renewal, backups, and upgrades.
Expand Down
2 changes: 1 addition & 1 deletion deploy/compose.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: ${SYNIXIR_STAGE_PROJECT:?Initialize staging with scripts/staging.py init}
name: ${SYNIXIR_STAGE_PROJECT:?Initialize staging with scripts/staging.exs init}

x-release: &release
image: ${SYNIXIR_IMAGE:?Set the locally built release image}
Expand Down
39 changes: 21 additions & 18 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@ HTTPS gateway and private Prometheus collector. It runs one application node.

## Start staging

Requirements: Docker with Compose v2, Python 3.11 or newer, OpenSSL, and Node.js
matching `examples/collaboration/.nvmrc` for the browser pilot. Docker builds Elixir,
Erlang and the frontend inside the image. Run these commands from the repository:
Requirements: Docker with Compose v2, OpenSSL, and Elixir 1.18+ with Erlang/OTP 27+.
Use the versions pinned in `.tool-versions`. The standalone staging scripts use
only the Elixir and Erlang standard libraries, so they need no `mix deps.get`.
The browser pilot also needs Node.js matching `examples/collaboration/.nvmrc`.
Docker builds the application and frontend inside the image. Run these commands
from the repository:

```sh
python3 scripts/staging.py init
python3 scripts/staging.py build
python3 scripts/staging.py up
python3 scripts/staging.py status
elixir scripts/staging.exs init
elixir scripts/staging.exs build
elixir scripts/staging.exs up
elixir scripts/staging.exs status
```

Open [local staging](https://localhost:8443) and create an account. The SDK settings
Expand All @@ -41,7 +44,7 @@ uses the `synixir-staging_database` volume. It does not use the development data
or ports 4000, 5173 or 5432. To select another port or image at initialization:

```sh
python3 scripts/staging.py init --port 8445 --image synixir:staging-v1
elixir scripts/staging.exs init --port 8445 --image synixir:staging-v1
```

Use `--directory /absolute/private/path` before the subcommand to select a saved
Expand Down Expand Up @@ -102,7 +105,7 @@ Install the browser pilot dependencies once:
```sh
npm ci
npx playwright install --only-shell chromium
python3 scripts/staging.py pilot
elixir scripts/staging.exs pilot
```

The pilot creates owner, editor and viewer accounts. It checks shared editing,
Expand All @@ -118,16 +121,16 @@ The private `pilot.json` retains fixture credentials and browser sessions.
pilot after an upgrade:

```sh
python3 scripts/staging.py check-pilot
python3 scripts/staging.py native-check
elixir scripts/staging.exs check-pilot
elixir scripts/staging.exs native-check
```

The pilot's document and memberships are fixtures. Editing them changes the
expected results. To run the entire rehearsal again without touching this
installation, CI and local development use a fresh, randomly named project:

```sh
python3 scripts/staging.py rehearse --image synixir:staging \
elixir scripts/staging.exs rehearse --image synixir:staging \
--output /tmp/synixir-release-pilot.json
```

Expand All @@ -138,8 +141,8 @@ for each run. It never deletes the persistent staging volume.
## Backups and replacement

```sh
python3 scripts/staging.py backup
python3 scripts/staging.py verify /absolute/path/to/backup.dump
elixir scripts/staging.exs backup
elixir scripts/staging.exs verify /absolute/path/to/backup.dump
```

Backups are full PostgreSQL custom archives with a SHA-256 sidecar. Verification
Expand All @@ -153,8 +156,8 @@ Build a distinct image tag for a candidate, then replace staging:

```sh
docker build --build-arg REVISION="$(git rev-parse HEAD)" -t synixir:staging-v2 .
python3 scripts/staging.py upgrade --image synixir:staging-v2
python3 scripts/staging.py check-pilot
elixir scripts/staging.exs upgrade --image synixir:staging-v2
elixir scripts/staging.exs check-pilot
```

For an uncommitted build, append `-dirty` to its revision label, as the `build`
Expand Down Expand Up @@ -185,8 +188,8 @@ arbitrary future migration compatibility.
## Stop and scope

```sh
python3 scripts/staging.py stop
python3 scripts/staging.py up
elixir scripts/staging.exs stop
elixir scripts/staging.exs up
```

Stopping retains the database volume, credentials and backups. Do not remove the
Expand Down
16 changes: 10 additions & 6 deletions docs/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,19 @@ reachability. CI also validates an actual application scrape using `check metric

## Back up and verify

Use Python 3.11 or later and PostgreSQL 17 client tools. The examples use the
repository's PostgreSQL container, `synixir-db-1`. `--docker-container` runs tools
Use Elixir 1.18+ with Erlang/OTP 27+ and PostgreSQL 17 client tools. The examples
use the repository's PostgreSQL container, `synixir-db-1`. `--docker-container` runs tools
inside that specific container against its local database. Omit it to use native
tools with libpq's `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSFILE` or `PGPASSWORD`.

The CLI uses only standard libraries. Restore verification, drills, and load
checks also run the application through Mix and require its dependencies.
Run `elixir scripts/operations_test.exs` to test the standalone script support.

```sh
python3 scripts/operations/database.py --docker-container synixir-db-1 backup --database synixir_dev --output /secure/backups/synixir.dump
python3 scripts/operations/database.py --docker-container synixir-db-1 verify /secure/backups/synixir.dump
python3 scripts/operations/database.py --docker-container synixir-db-1 drill --output /tmp/synixir-restore.json
elixir scripts/operations/database.exs --docker-container synixir-db-1 backup --database synixir_dev --output /secure/backups/synixir.dump
elixir scripts/operations/database.exs --docker-container synixir-db-1 verify /secure/backups/synixir.dump
elixir scripts/operations/database.exs --docker-container synixir-db-1 drill --output /tmp/synixir-restore.json
```

Create the output directory first and choose a new filename on each run. Backups
Expand Down Expand Up @@ -186,7 +190,7 @@ external storage policy.
## Load check

```sh
python3 scripts/operations/database.py --docker-container synixir-db-1 load --clients 12 --rooms 3 --writes 100 --output /tmp/synixir-load.json
elixir scripts/operations/database.exs --docker-container synixir-db-1 load --clients 12 --rooms 3 --writes 100 --output /tmp/synixir-load.json
docker run --rm -i --network none --entrypoint promtool prom/prometheus:v3.5.5 check metrics < /tmp/synixir-load.json.prom
```

Expand Down
79 changes: 79 additions & 0 deletions scripts/operations/database.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env elixir
Code.require_file("../support/database.exs", __DIR__)

defmodule Synixir.Script.DatabaseCLI do
alias Synixir.Script, as: S
alias Synixir.Script.Database

@usage """
Usage: elixir scripts/operations/database.exs [--docker-container NAME] COMMAND
backup --database NAME --output PATH
verify ARCHIVE
drill --output PATH
load --output PATH [--clients 12] [--rooms 3] [--writes 20] [--port 4012]
"""

def main(argv) do
case S.parse(
argv,
[
docker_container: :string,
database: :string,
output: :string,
clients: :integer,
rooms: :integer,
writes: :integer,
port: :integer
],
~w(backup verify drill load),
@usage
) do
:help -> :ok
{options, args} -> execute(options, args)
end
end

defp execute(options, args) do
container = options[:docker_container]
output = if options[:output], do: options[:output] |> Path.expand() |> S.unused!()

result =
case args do
["backup"] ->
Database.backup(
container,
S.required(options, :database),
output || S.required(options, :output)
)

["verify", archive] ->
Database.verify(container, Path.expand(archive))

["drill"] ->
S.required(options, :output)
result = Database.drill(container)
S.private_json(output, result)
result

["load"] ->
S.required(options, :output)
S.unused!(output <> ".prom")

Database.load(
container,
options[:clients] || 12,
options[:rooms] || 3,
options[:writes] || 20,
options[:port] || 4012,
output
)

_ ->
raise ArgumentError, @usage
end

S.print(result)
end
end

Synixir.Script.cli(fn -> Synixir.Script.DatabaseCLI.main(System.argv()) end)
Loading
Loading