Atom

Quickstart

Get Atom running and make your first authorization check.

Prerequisites

  • Docker and Docker Compose
  • make
  • jq for command examples
  • openssl only if you enable certificates (off by default in dev env)

Steps

Create Local Dev Config

cp .env.example .env

There is one config file. .env.example ships working local defaults — admin login admin / 12345678, password login allowed before email verification, and certificates disabled — so a fresh copy boots with no SMTP, OAuth, or CA setup. Edit ADMIN_SECRET before first boot to change the admin password.

Start Atom

make up

This starts (building the images the first time, then reusing them):

  • Atom REST and GraphQL at http://localhost:8080
  • Atom Next UI at http://localhost:3005
  • Postgres at 127.0.0.1:5432

After changing backend or UI code, rebuild with make build (or make atom-build / make ui-build) before make up.

Watch Logs

make logs

Atom applies migrations automatically on startup.

Log In

TOKEN=$(curl -s -X POST http://localhost:8080/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"identifier": "admin", "secret": "12345678"}' \
  | jq -r .token)

Create A Tenant, Device, And Channel

Use GraphQL at POST http://localhost:8080/graphql.

curl -s http://localhost:8080/graphql \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "query": "mutation { createTenant(input: { name: \"factory-a\", route: \"factory-a\" }) { id name route } }"
  }'

Create an entity for a device:

mutation {
  createEntity(input: {
    kind: device
    name: "meter-001"
  }) {
    id
    name
    kind
  }
}

Create a channel resource:

mutation {
  createResource(input: {
    kind: "channel"
    name: "telemetry"
  }) {
    id
    kind
    name
  }
}

Give Publish Access

Find the seeded publish action:

{
  actions(objectKind: "resource", objectType: "resource:channel") {
    items {
      id
      name
    }
  }
}

Create a permission block for the channel:

mutation {
  createPermissionBlock(input: {
    scopeMode: "object"
    objectId: "channel-resource-id"
    effect: allow
    actionIds: ["publish-action-id"]
  }) {
    id
    effect
    actions { name }
  }
}

Create a role and attach the permission block:

mutation {
  createRole(input: {
    name: "channel-publisher"
    description: "Can publish telemetry"
  }) {
    id
    name
  }
}
mutation {
  replaceRolePermissionBlocks(
    roleId: "role-id"
    permissionBlockIds: ["permission-block-id"]
  )
}

Assign the role to the device:

mutation {
  createRoleAssignment(input: {
    subjectKind: entity
    subjectId: "device-entity-id"
    roleId: "role-id"
  }) {
    id
    subjectId
    roleId
  }
}

Check Authorization

mutation {
  authzCheck(input: {
    subjectId: "device-entity-id"
    action: "publish"
    resourceId: "channel-resource-id"
  }) {
    allowed
    reason
  }
}

Expected result:

{
  "allowed": true,
  "reason": "allowed"
}

Makefile Commands

Run make help for the current target list.

CommandWhat it does
make dbStarts only Postgres (for a host cargo run).
make devHost cargo run (:8090) + UI dev (:3000) on the shared Postgres; runs with make up.
make upStarts Postgres, Atom, and Atom UI (builds images only if missing).
make logsFollows Atom backend and Atom UI logs.
make downStops the local Compose stack.
make restartStops and starts the Compose stack again (no rebuild).
make buildBuilds Atom backend and Atom UI images.
make atom-buildBuilds only the Atom backend image.
make ui-buildBuilds only the Atom UI image.
make docker-buildBuilds the raw Atom Docker image using Makefile variables.
make docker-build-releaseBuilds the raw release Docker image.

Common overrides:

# Use another env file
DEV_ENV_FILE=.env.local make up
 
# Use different host ports
POSTGRES_HOST_PORT=55432 ATOM_HTTP_PORT=28080 ATOM_UI_HTTP_PORT=3006 make up
 
# Start only backend services, without the UI profile
COMPOSE_PROFILES="--profile default" make up

Direct Cargo Development

Run the Rust service on your host and keep only Postgres in Docker. Postgres is published on 127.0.0.1:5432 and .env points DATABASE_URL at localhost:5432, so this works with no extra setup:

make db        # start only Postgres
cargo run      # Atom on http://localhost:8080

Plain cargo run uses LISTEN_ADDR from .env (8080), so it collides with make up. To run both at once, use make dev (below), which moves the host backend to a separate port.

UI Development And Running Everything At Once

The host dev flow uses its own ports so it can run alongside make up on the same Postgres. make dev starts Postgres (Docker) plus Atom and the Next UI on the host (Ctrl-C stops both; needs host cargo and pnpm):

make dev                 # cargo run (:8090) + pnpm dev (:3000), Postgres shared
FlowBackendUIPostgres
make up (Compose):8080:3005:5432
make dev (host):8090:3000:5432 (same DB)

Log in to either with the same admin credentials (admin / 12345678); both read ADMIN_SECRET from .env and share one database.

Run both together to compare a code change against the released image — they share one Postgres volume. Override with DEV_HTTP_PORT / DEV_UI_PORT.

To run the UI pieces yourself instead:

make db && cargo run     # backend on :8080
 
cd app
pnpm install
ATOM_GRAPHQL_URL=http://localhost:8080/graphql pnpm dev   # UI on :3000

The dev UI reads the backend GraphQL endpoint from ATOM_GRAPHQL_URL (server-side). Origins :3000 and :3005 are already allowed by the default ATOM_CORS_ALLOWED_ORIGINS in .env.

What Just Happened

  1. You created an entity for a device.
  2. You created a resource for a channel.
  3. You created a permission block that allows publish on that channel.
  4. You put the permission block inside a role.
  5. You assigned the role to the device.
  6. Atom returned allow when asked whether the device can publish.

See Atom In Simple Words for the layman explanation, and Access Control for the full model.

On this page