Tamanu is an open-source patient-level electronic health records system for mobile and desktop.
The Meta service provides:
- a server discovery service for the Tamanu mobile app
- a server list and health check page
- a range of active versions
We have a container image for linux/amd64 and linux/arm64:
ghcr.io/beyondessential/tamanu-meta:3.4.0
Routes marked with π require authentication; the word in (parens) after the emoji is the required role
; admin
role can do everything.
The mtls-certificate
(or ssl-client-cert
) header should contain a PEM-encoded (optionally URL-encoded) X509 certificate.
Alternatively, Rocket can be configured to terminate TLS itself, and handles the client certificate itself directly.
In this case, the certificate must be signed by the provided CA (enabled by default.tls.mutual
) to pass validation; you'll need to generate a CA with smallstep CLI in that case, this is not covered here.
To get a certificate, run:
$ cargo run --bin identity
Which will write the identity.crt.pem
and identity.key.pem
.
You can then put it in an environment variable:
$ export MTLS_CERT="$(jq -sRr @uri identity.crt.pem)"
and then use curl like:
$ curl -H "mtls-certificate: $MTLS_CERT" ...
When you first connect to an authenticated API with a certificate, you'll get a 403.
Your public key will be added to the devices
table.
Open the database, e.g. with PSQL, and change the role to admin
(or as required).
UPDATE devices SET role = 'admin' WHERE id = '45886aa8-dff3-4cf7-92a9-31f42d4a0e1a';
In production, you need to do extra checks.
- Show untrusted devices with their public key in PEM-ish format:
SELECT id, created_at, encode(key_data, 'base64') as pem
FROM devices WHERE role = 'untrusted'
ORDER BY created_at DESC \gx
- Compare the provided public key to the list to find the right
id
. You can also filter by the last few characters of the key (the first characters will all be the same):
SELECT id, created_at, encode(key_data, 'base64') as pem
FROM devices WHERE role = 'untrusted'
AND encode(key_data, 'base64') LIKE '%NWwjGDiHVWrBA=='
ORDER BY created_at DESC \gx
- Once you have the
id
, look at the connection metadata to see if it matches what you know for additional verification:
SELECT * FROM device_connections
WHERE id = '45886aa8-dff3-4cf7-92a9-31f42d4a0e1a'
ORDER BY created_at DESC \gx
In production, the header should be set from a client certificate, as terminated by a reverse proxy or load balancer, and any matching header on the incoming requests should be stripped.
- Nginx: use the
$ssl_client_escaped_cert
variable. - Caddy: use the
{http.request.tls.client.certificate_pem}
placeholder.
Get the full list of servers as JSON.
[
{
"id":"8960470f-5282-496e-86f5-21df8cf67d62",
"name":"Dev (main)",
"host":"https://central.main.internal.tamanu.io/",
"rank":"dev"
}
]
Add a server to the list.
Pass a JSON body:
{
"name":"Dev (main)",
"host":"https://central.main.internal.tamanu.io/",
"rank":"dev"
}
Returns the server with its assigned ID:
{
"id":"8960470f-5282-496e-86f5-21df8cf67d62",
"name":"Dev (main)",
"host":"https://central.main.internal.tamanu.io/",
"rank":"dev"
}
Edit a server.
Pass a JSON body, all fields optional except for id
:
{
"id":"8960470f-5282-496e-86f5-21df8cf67d62",
"name":"Test server (main)"
}
Returns the edited server with its assigned ID:
{
"id":"8960470f-5282-496e-86f5-21df8cf67d62",
"name":"Test server (main)",
"host":"https://central.main.internal.tamanu.io/",
"rank":"dev"
}
There's a "hidden" feature in the UI where if you Shift-click a row, it will copy its ID to the clipboard.
Remove a server from the list.
Pass a JSON body with the id
field:
{
"id":"8960470f-5282-496e-86f5-21df8cf67d62"
}
Force a reload of the statuses.
Get a list of known Tamanu versions in JSON.
[
{
"id": "967c7d15-7046-459e-a448-6584f72e55ce",
"major": 2,
"minor": 27,
"patch": 0,
"published": true,
"changelog": "..."
},
// ...
]
Create the version <version>
(must be a semver version string).
Pass the changelog as the body (if using curl, use --data-binary
to preserve whitespace):
Blah
blah
blah
Returns the version with its assigned ID:
{
"id": "967c7d15-7046-459e-a448-6584f72e55ce",
"major": 2,
"minor": 27,
"patch": 0,
"published": true,
"changelog": "..."
}
TO BE DOCUMENTED
TO BE DOCUMENTED
TO BE DOCUMENTED
- Install Rustup, which will install Rust and Cargo.
- Install the diesel CLI tool: https://diesel.rs/guides/getting-started.html#installing-diesel-cli
- Clone the repo via git:
$ git clone [email protected]:beyondessential/tamanu-meta-server.git
- Build the project:
$ cargo check
-
Create a new blank postgres database.
-
Create a
Rocket.toml
from theRocket.example.toml
. -
Set the
DATABASE_URL
environment variable (for diesel). You can do that per diesel command, or for your entire shell session usingexport
(orset -x
in fish, or$env:DATABASE_URL =
in powershell) as usual for your preferred shell. -
Run migrations:
$ diesel migration run
- Run with:
$ cargo run
- Tests:
$ cargo test
We recommend using Rust Analyzer or Rust Rover for development.
- Create a migration
$ diesel migration generate some_name_here
-
Write the migration's
up.sql
anddown.sql
-
Run the pending migrations:
$ diesel migration run
- Test your down:
$ diesel migration redo
- Run formatter:
$ cargo fmt
(You need write access to the main branch directly)
On the main branch:
$ cargo release --execute minor // or patch, major
Install cargo-release
with:
$ cargo install cargo-release
Also install git-cliff
:
$ cargo install git-cliff