Deploying OR stack on BalenaOS

Ill make this a little bit of a tutorial along with issues faced.

So the setup is Openremote on an Intel NUC running BalenaOS to function as an edge device.

Building stack
The custom deployment was built in the IDE and docker images created using:

  • DEPLOYMENT_VERSION=$(git rev-parse --short HEAD)
  • MANAGER_VERSION=$(cd openremote; git rev-parse --short HEAD; cd …)
  • docker build -t openremote/manager:$MANAGER_VERSION ./openremote/manager/build/install/manager/
  • docker build -t openremote/custom-deployment:$DEPLOYMENT_VERSION ./deployment/build/*

Now running the stack on docker desktop:

  • DEPLOYMENT_VERSION=local docker image ID MANAGER_VERSION=local docker image ID OR_ADMIN_PASSWORD=secret OR_HOSTNAME=localhost docker-compose -p custom up -d

And everything runs fine.

Deploying to BalenaOS

  1. To install BalenaOS on an intel nuc, Balena has a nice tutorial: Get Started with balenaCloud using Intel NUC and Node.js - Balena Documentation

  2. In the tutorial, a single container is built from a dockerfile. To deploy multiple prebuilt images, the command “balena deploy” must be used inside the folder containing your docker-compose.yml

  3. You can run this allong with a X11 + browser container such as GitHub - balena-labs-projects/browser: A drop-in web browser block to access OR on the device itself.

  4. Before deployment, environment variables must be set.

Write environment variables via .env file
Environment variables can be set within balenacloud manually, however here is a approach to load them from a file.

  1. Make a file containing your environment variables, such as balena.env .
  2. Make sure line ending format is Unix style
  3. Separate the variables and the values by a space, don’t use an equal sign (eg. OR_HOSTNAME localhost).
  4. Now run the command within the folder containing the .env:
    xargs -a .env -t -I % /bin/bash -c “balena env add % --fleet MY_FLEET”
  5. This pushes the environment variables to your balena fleet.

Open Issues

  1. Balena checks the docker-compose and it does not support all functions
    Supported functions: docker-compose.yml fields - Balena Documentation
    -condition: service_healthy is not supported, only service_started. Changing to service_started fixes this, but will create other issues I guess.

  2. I cant get proper access to OR over the network.

  • /manager/ results in OR page showing “Invalid parameter: redirect-uri”
  • keycloak log reports the same error: error=invalid_redirect_uri
  • when only specifying OR_HOSTNAME=localhost, the redirect goes to http://localhost/auth/realms , even when approached over the network.
  • specifically changing KC_HOSTNAME to the IP adress of the device does change the redirect but results in the same error
  • /auth/ can be reached on the ip in either case but the admin console is empty.
2 Likes

About the second issue you’re having, if I remember correctly, you can use the IPv4 address of your device as OR_HOSTNAME. If I remember correctly again, I think that when the reverse proxy sees that you’re using an IPv4 address instead of an FQDN, it won’t attempt to get a certificate, but still use that IPv4 address to connect to OpenRemote. By using localhost as your OR_HOSTNAME, it’ll only allow connections made within your computer. Attempting to set your network address as your OR_HOSTNAME could solve the second issue.

About the first issue, what changes is until when Docker waits to start each container. You could use depends_on on every container, but YMMV.

Best of luck!

Hi Wouter,

Thanks for providing this useful information.

Let’s start with the invalid_redirect_uri so keycloak realms are configured with redirect URIs, these are valid origins that are allowed to log into keycloak and this is where keycloak will redirect back to after login.

By default the OR_HOSTNAME and OR_ADDITIONAL_HOSTNAMES will be added to this list of redirect uris.

I don’t know if balena provides a DNS record for each device and whether you can include that in the env file with some kind of variable?

Basically whatever origin you put into your web browser to access the manager on the balena device will need to be included in the OR_HOSTNAME or OR_ADDITIONAL_HOSTNAMES env variables.

Let’s see if this gets you to a valid login page.

I’ve currently setup these variables. With these now the login screen appears, all tough with a different appearance and when inside the manager there is no admin access, even though logged in with admin/secret.
Changing OR_HOSTNAME to 192.168.1.126 keeps resulting in the redirect error.

The device does get a DNS record and exposes port 80, however this requires some more setup from balena that I will need to figure out, I’d like to start getting access from the local network.

Hi,

If you are seeing a loging page with a different style then that suggests the manager UI is falling back to the basic auth login which happens if keycloak cannot be reached, developer tools window will confirm this.

Can you please paste the full URL from your browser when you see the redirect error.

Yes, I’ve seen that in the logs before.

URL: https://192.168.1.126/auth/realms/master/protocol/openid-connect/auth?client_id=openremote&redirect_uri=https%3A%2F%2F192.168.1.126%2Fmanager%2F&state=4cd3772f-8846-4aca-84cb-e81c3315ebfb&response_mode=fragment&response_type=code&scope=openid&nonce=6fd9ca28-0293-442a-821a-df506fddfac8

That redirect URI looks ok so it suggests an issue with configuration; can you paste the docker compose file you are using and also worth noting by default the manager won’t run setup code if the database already contains data.

It is the setup code run by the manager that does the initial configuration of keycloak (create and configure realms); it is during this phase that the redirect URI is set.

If you remove the deployment-data docker volume then start the stack the setup code will run again.

Alternatively you could manually change the keycloak realm configuration if you can access the keycloak admin console at /auth, make sure the KC_HOSTNAME env variable is correctly configured.

FYI: OR_HOSTNAME should never be localhost in your situation as you have no UI on the balena device; I would defininitely use the IP address of the device for this as it is also used for KC_HOSTNAME and keycloak doesn’t support multiple hostnames so whatever is used in KC_HOSTNAME should be a value that is always accessible from any client.

The docker-compose:

services:

  # This service will only populate an empty volume on startup and then exit.
  # If the volume already contains data, it exits immediately.
  deployment:
    image: openremote/custom-deployment:62c8ed1
    volumes:
      - deployment-data:/deployment
    environment:
      OR_ADMIN_PASSWORD: secret

  proxy:
    image: openremote/proxy:latest
    restart: always
    depends_on:
      manager:
        condition: service_started
    ports:
      - "80:80"
      - "443:443"
      - "8883:8883"
    volumes:
      - proxy-data:/deployment
      - deployment-data:/data
    environment:
      LE_EMAIL: ${OR_EMAIL_ADMIN}
      DOMAINNAME: ${OR_HOSTNAME?OR_HOSTNAME must be set}
      DOMAINNAMES: ${OR_ADDITIONAL_HOSTNAMES:-}
      # USE A CUSTOM PROXY CONFIG - COPY FROM https://github.com/openremote/proxy/blob/main/haproxy.cfg
      #HAPROXY_CONFIG: '/data/proxy/haproxy.cfg'


  postgresql:
    image: openremote/postgresql:latest
    restart: always
    volumes:
      - postgresql-data:/var/lib/postgresql/data
      - manager-data:/storage

  keycloak:
    image: openremote/keycloak:latest
    restart: always
    depends_on:
      postgresql:
        condition: service_started
    volumes:
      - deployment-data:/deployment
    environment:
      KEYCLOAK_ADMIN_PASSWORD: ${OR_ADMIN_PASSWORD:?OR_ADMIN_PASSWORD must be set}
      KC_HOSTNAME: ${OR_HOSTNAME:-localhost}
      KC_HOSTNAME_PORT: ${OR_SSL_PORT:--1}

  manager:
    image: openremote/manager:d694d0762
    restart: always
    depends_on:
      keycloak:
        condition: service_started
    volumes:
      - manager-data:/storage
      - deployment-data:/deployment
      # Map data should be accessed from a volume mount
      # 1). Host filesystem - /deployment.local:/deployment.local
      # 2) NFS/EFS network mount - efs-data:/efs
    environment:
      # Here are some typical environment variables you want to set
      # see openremote/profile/deploy.yml for details
      OR_ADMIN_PASSWORD: ${OR_ADMIN_PASSWORD?OR_ADMIN_PASSWORD must be set}
      OR_SETUP_TYPE: #production # Typical values to support are staging and production
      OR_SETUP_RUN_ON_RESTART:
      OR_EMAIL_HOST:
      OR_EMAIL_USER:
      OR_EMAIL_PASSWORD:
      OR_EMAIL_X_HEADERS:
      OR_EMAIL_FROM:
      OR_EMAIL_ADMIN:
      OR_HOSTNAME: ${OR_HOSTNAME?OR_HOSTNAME must be set}
      OR_ADDITIONAL_HOSTNAMES: ${OR_ADDITIONAL_HOSTNAMES:-}
      OR_SSL_PORT: ${OR_SSL_PORT:--1}
      OR_DEV_MODE: ${OR_DEV_MODE:-false}
      OR_MAP_TILES_PATH: '/efs/europe.mbtiles'
  #
  #
  #  - Adding window manager -
  windowmanager:
    build: ./x11-window-manager
    restart: always
    network_mode: "host"
    privileged: true
    environment:
        DISPLAY: ${DISPLAY}

I’ve removed the volumes as you suggested, both on the NUC and local docker.

The NUC manager now cannot connect with keycloak due to:

 manager  2024-02-05 14:28:47.971  WARNING [main                          ] emote.container.web.OAuthFilter.PROTOCOL : OAuth server response error: 401
 manager  2024-02-05 14:28:47.972  INFO    [main                          ] curity.keycloak.KeycloakIdentityProvider : Credentials are invalid
 manager  2024-02-05 14:28:47.972  WARNING [main                          ] curity.keycloak.KeycloakIdentityProvider : Credentials don't work so cannot continue

Running the same compose locally on my machine does not result in this error, logs credentials are valid.
But now fails to find the client in the browser, manager log:

2024-02-05 13:40:49 2024-02-05 13:40:49.993  WARNING [WebService task-2             ] curity.keycloak.KeycloakIdentityProvider : Error loading client 'openremote' for realm 'master' from identity provider, exception from call to identity provider follows
2024-02-05 13:40:49 com.google.common.util.concurrent.UncheckedExecutionException: jakarta.ws.rs.NotAuthorizedException: HTTP 401 Unauthorized

I’m getting a little lost here :sweat_smile:

Not sure why you are seeing different behaviour on the NUC vs local.

That error message often indicates that the OR_ADMIN_PASSWORD doesn’t match the password set on keycloak, just to define some logical steps here:

docker compose -p or down
docker volume prune <--- Remove all volumes no longer used by containers
docker compose -p or up

With the above, ensure your OR_HOSTNAME in env file is set to the IP address (you can add localhost to OR_ADDITIONAL_HOSTNAMES if desired so you can use a browser directly on the NUC).

Any additional problems it might be worth us organising a call and screen share.

To wrap up this topic, the issues were addressed :slight_smile:

  1. Balena does not support variable substitution, so change all substitutions to their required values manually.
  2. Balena does not support service depedency “service-healthy”, change it to “service-started”. This has been tested and seems to work fine.

Here are compose and .env examples that work:

version: '2.4'

volumes:
  proxy-data:
  deployment-data:
  postgresql-data:
  manager-data:

services:

  deployment:
    image: openremote/custom-deployment:62c8ed1
    volumes:
      - deployment-data:/deployment

  proxy:
    image: openremote/proxy:latest
    restart: always
    depends_on:
      manager:
        condition: service_started
    ports:
      - "80:80"
      - "443:443"
      - "8883:8883"
    volumes:
      - proxy-data:/deployment
      - deployment-data:/data

  postgresql:
    image: openremote/postgresql:latest
    restart: always
    volumes:
      - postgresql-data:/var/lib/postgresql/data
      - manager-data:/storage

  keycloak:
    image: openremote/keycloak:latest
    restart: always
    depends_on:
      postgresql:
        condition: service_started
    volumes:
      - deployment-data:/deployment

  manager:
    image: openremote/manager:latest
    restart: always
    depends_on:
      keycloak:
        condition: service_started
    volumes:
      - manager-data:/storage
      - deployment-data:/deployment

.env :

OR_HOSTNAME 192.168.1.138
KC_HOSTNAME 192.168.1.138
OR_ADDITIONAL_HOSTNAMES localhost
OR_SSL_PORT 443
KC_HOSTNAME_PORT -1
WEBSERVER_LISTEN_HOST 0.0.0.0

OR_ADMIN_PASSWORD secret
KEYCLOAK_ADMIN_PASSWORD secret
OR_EMAIL_ADMIN none
LE_EMAIL none

OR_DEV_MODE false
OR_SETUP_TYPE production
2 Likes