Skip to content
Open
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
153 changes: 153 additions & 0 deletions OPERATIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Operations

Production server management for the st0x REST API.

## Server Access

```sh
ssh root@<SERVER_IP>
```

## Service Management

The API runs as a systemd service called `rest-api.service`.

```sh
# Status
systemctl status rest-api.service

# Restart
systemctl restart rest-api.service

# Stop / Start
systemctl stop rest-api.service
systemctl start rest-api.service
```

## Logs

Live logs:

```sh
journalctl -u rest-api.service -f
```

Historical logs (last hour, last 100 lines, since a date):

```sh
journalctl -u rest-api.service --since "1 hour ago"
journalctl -u rest-api.service -n 100
journalctl -u rest-api.service --since "2026-03-01"
```

### File-based logs

All tracing output is also written as structured JSON to daily-rotating log files. The log directory is configured via `log_dir` in the config file.

Production log directory: `/mnt/data/st0x-rest-api/logs`

Files follow the pattern `st0x-rest-api.log.YYYY-MM-DD` — a new file is created each day:

```
st0x-rest-api.log.2026-03-08
st0x-rest-api.log.2026-03-09
st0x-rest-api.log.2026-03-10
```

To browse them:

```sh
ls -lt /mnt/data/st0x-rest-api/logs/
tail -f /mnt/data/st0x-rest-api/logs/st0x-rest-api.log.$(date +%Y-%m-%d)
```

Since the files are JSON, you can use `jq` to filter:

```sh
# All errors from today
cat /mnt/data/st0x-rest-api/logs/st0x-rest-api.log.$(date +%Y-%m-%d) | jq 'select(.level == "ERROR")'

# Requests to a specific endpoint
cat /mnt/data/st0x-rest-api/logs/st0x-rest-api.log.$(date +%Y-%m-%d) | jq 'select(.fields.uri | contains("/v1/tokens"))'
```

Locally, the same file-based logs are written to `./logs/` (relative to where you run the server).

## Configuration

The production config is passed via the `ExecStart` line in the service file. To find the current config path:

```sh
systemctl cat rest-api.service | grep ExecStart | grep -o '\-\-config [^ ]*' | cut -d' ' -f2
```

Production database: `sqlite:///mnt/data/st0x-rest-api/st0x.db`

## Production Key Management

Key management commands on the server use the production config path. First, find it:

```sh
CONFIG=$(systemctl cat rest-api.service | grep ExecStart | grep -o '\-\-config [^ ]*' | cut -d' ' -f2)
```

Then use the binary directly:

```sh
BINARY=/nix/var/nix/profiles/per-service/rest-api/bin/st0x_rest_api

# Create a standard key
$BINARY keys --config $CONFIG create --label "partner-name" --owner "contact@example.com"

# Create an admin key (required for /admin/* endpoints)
$BINARY keys --config $CONFIG create --label "ops-admin" --owner "ops@example.com" --admin

# List keys
$BINARY keys --config $CONFIG list

# Revoke a key
$BINARY keys --config $CONFIG revoke <KEY_ID>

# Delete a key
$BINARY keys --config $CONFIG delete <KEY_ID>
```

## Registry Updates

The strategy registry URL can be updated at runtime via the admin API:

```sh
curl -X PUT \
-u "ADMIN_KEY_ID:ADMIN_SECRET" \
-H "Content-Type: application/json" \
-d '{"registry_url":"https://raw.githubusercontent.com/rainlanguage/rain.strategies/<COMMIT>/registry"}' \
https://<SERVER_IP>/admin/registry -k
```

Admin credentials are shared separately and should never be committed.

There is no dedicated admin-key rotation command. Rotate admin credentials manually:

1. Create a new admin key with `--admin`
2. Update the systems or operators using the old credentials
3. Revoke or delete the old admin key

## Deployment

Deployments are triggered manually via the **Deploy** workflow in GitHub Actions (`cd.yaml`, `workflow_dispatch`). To deploy:

1. Go to the repo's **Actions** tab
2. Select the **Deploy** workflow
3. Click **Run workflow** on the target branch

The workflow builds the Nix package and deploys it to the server via `deploy-rs`.

## API Documentation

User-facing API docs are built with mdbook from `docs/src/`. On the server, the built docs are served from `/var/lib/st0x-docs`.

To update the docs:

1. Edit files in `docs/src/`
2. Rebuild locally: `nix develop -c mdbook build docs`
3. Deploy (docs are included in the deployment)
89 changes: 45 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

REST API for st0x orderbook operations. Built with Rocket, backed by SQLite, and authenticated via API keys using HTTP Basic auth.

## Prerequisites

- [Nix](https://nixos.org/download/) with flakes enabled

## Setup

### 1. Clone and initialize submodules
Expand All @@ -18,92 +22,89 @@ git submodule update --init --recursive
nix develop -c bash prep.sh
```

This writes `COMMIT_SHA` to `.env` and bootstraps the orderbook submodule.

## Usage
This initializes submodules, runs the orderbook prep script, and builds the local mdBook into `docs/book` for the `/docs` route. Run it before the first `serve`.

The binary has two subcommands:
### 3. Configure

```
st0x_rest_api serve Start the API server
st0x_rest_api keys Manage API keys
```
The config file is at `config/dev.toml`. The only value that may need updating is `registry_url` — it points to a pinned GitHub raw URL so it should work out of the box.

### Starting the server
If it's stale, grab the latest from production:

```sh
nix develop -c cargo run serve
curl -u "KEY_ID:SECRET" https://<SERVER_IP>/registry -k
```

The server starts on `http://localhost:8000` by default. Swagger UI is available at `/swagger`.
and update `registry_url` in `config/dev.toml`.

### API key management

All API routes (except `/health`) require HTTP Basic authentication. Use the `keys` subcommand to manage credentials.

#### Create a key
### 4. Create an API key

```sh
nix develop -c cargo run keys create --label "partner-x" --owner "contact@example.com"
nix develop -c cargo run -- keys --config config/dev.toml create \
--label "dev-key" \
--owner "you@example.com"
```

Output:

```
API key created successfully
This prints a **Key ID** (UUID) and **Secret** (base64 string). Save both — the secret is hashed with Argon2 and cannot be recovered.

Key ID: <uuid>
Secret: <base64-encoded-secret>
Label: partner-x
Owner: contact@example.com
### 5. Set log level (optional)

IMPORTANT: Store the secret securely. It will not be shown again.
```sh
export RUST_LOG=st0x_rest_api=info,rocket=warn,warn
```

The secret is hashed with Argon2 before storage. There is no way to recover it.

#### List keys
### 6. Start the server

```sh
nix develop -c cargo run keys list
COMMIT_SHA=$(git rev-parse HEAD) nix develop -c cargo run -- serve --config config/dev.toml
```

Shows all keys with their ID, label, owner, active status, and timestamps.
Server runs on `http://127.0.0.1:8000`. Swagger UI is available at `/swagger`, and the mdBook docs are served at `/docs`.

#### Revoke a key
The database (`data/st0x.db`) is created and migrated automatically on first run — no manual DB setup needed.

### 7. Test it

```sh
nix develop -c cargo run keys revoke <KEY_ID>
curl -u "KEY_ID:SECRET" http://localhost:8000/v1/tokens
```

Sets the key to inactive. Revoked keys are rejected at authentication.
API routes require HTTP Basic Auth with the key ID and secret. `/health`, `/docs`, `/swagger`, and `/api-doc/openapi.json` are public.

## API Key Management

#### Delete a key
Use the `keys` subcommand to manage credentials. All commands require `--config` to point at the config file.

```sh
nix develop -c cargo run keys delete <KEY_ID>
```
# List keys
nix develop -c cargo run -- keys --config config/dev.toml list

# Revoke a key (sets it to inactive, rejected at auth)
nix develop -c cargo run -- keys --config config/dev.toml revoke <KEY_ID>

Permanently removes the key from the database.
# Delete a key (permanent removal)
nix develop -c cargo run -- keys --config config/dev.toml delete <KEY_ID>
```

### Authenticating API requests
## Authenticating API Requests

Use HTTP Basic auth with the key ID as the username and the secret as the password:

```sh
curl -u "<KEY_ID>:<SECRET>" http://localhost:8000/v1/tokens
curl -u "KEY_ID:SECRET" http://localhost:8000/v1/tokens
```

Or with an explicit header:

```sh
curl -H "Authorization: Basic $(echo -n '<KEY_ID>:<SECRET>' | base64)" http://localhost:8000/v1/tokens
curl -H "Authorization: Basic $(echo -n 'KEY_ID:SECRET' | base64)" http://localhost:8000/v1/tokens
```

## Development

```sh
nix develop -c cargo fmt
nix develop -c rainix-rs-static
nix develop -c cargo test
nix develop -c cargo fmt # format
nix develop -c rainix-rs-static # lint
nix develop -c cargo test # test
```

Always run the formatter and linter before committing.
Loading