Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
cdcde9c
hawkBit integration as extension
dominiquekleeven May 4, 2026
1e4aee6
Rollouts and target filters
dominiquekleeven May 7, 2026
c309453
License headers
dominiquekleeven May 7, 2026
35e7ba9
Add attribute to hawkBit target.metadata sync support
dominiquekleeven May 11, 2026
4f79714
Add documentation, with compose and haproxy examples.
dominiquekleeven May 11, 2026
8410fd3
Merge branch 'main' into feature/hawkbit-extension
dominiquekleeven May 11, 2026
3ece36e
Refactor into a proxy style, remainder dto's changed to records.
dominiquekleeven May 13, 2026
1950462
Extend link mapping with name extraction if present
dominiquekleeven May 13, 2026
6dde76d
Clean up, improved proxy request building, and use resteasy multipart
dominiquekleeven May 14, 2026
c7a412a
Restructuring, minor refactor on the response handling/proxying + add…
dominiquekleeven May 15, 2026
278609d
Update README.md
dominiquekleeven May 15, 2026
dc0094a
Refactor more into the proxy model
dominiquekleeven May 15, 2026
77b19d9
Add javadocs, add realm guard for all endpoints
dominiquekleeven May 18, 2026
07f2586
Cleanup
dominiquekleeven May 18, 2026
64a64c4
Return target on create, remove unnecessary getTarget calls.
dominiquekleeven May 20, 2026
60e757b
Allow providing a custom securityToken when creating a hawkBit target…
dominiquekleeven May 20, 2026
752712e
Add createUpdateTarget method, allows updating target properties if i…
dominiquekleeven May 20, 2026
72478ed
Add missing guard in createTarget
dominiquekleeven May 25, 2026
d6f2f1e
Add action status endpoint
dominiquekleeven May 27, 2026
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
218 changes: 218 additions & 0 deletions hawkbit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
# hawkBit

## Introduction

[hawkBit](https://www.eclipse.org/hawkbit/) is a software update server for connected devices.

This extension connects OpenRemote Manager to the hawkBit Management API, exposes firmware endpoints in OpenRemote, and syncs selected assets as hawkBit targets.

## Limitations

**Single realm only.** The extension is bound at startup to one OpenRemote realm, set by `HAWKBIT_REALM` (default `master`). Asset sync, metadata sync, and the firmware API endpoints only operate on that realm. Callers from other realms get `403`.

Multi-tenant deployments are not supported, and is currently out of scope.

## Prerequisites

You need:

1. A hawkBit instance reachable from OpenRemote Manager
2. Management API credentials for hawkBit
3. An OpenRemote realm that should be synced with hawkBit
4. Assets configured with the firmware meta items described below

## Configuration

The following environment variables can be set on the OpenRemote manager:

| Variable | Required | Description |
|---|---|---|
| `HAWKBIT_MANAGEMENT_API_URL` | No | hawkBit Management API URL (default: `http://localhost:8083/hawkbit/rest/v1`) |
| `HAWKBIT_USERNAME` | No | hawkBit Management API username (default: `hawkbit`) |
| `HAWKBIT_PASSWORD` | No | hawkBit Management API password (default: `hawkbit`) |
| `HAWKBIT_REALM` | No | OpenRemote realm to sync with hawkBit (default: `master`) |

### Docker Compose Example

hawkBit can be started with a separate PostgreSQL database and persistent artifact storage:

```yaml
volumes:
hawkbitdb-data:
hawkbit-artifact-data:

services:
hawkbitdb:
image: openremote/postgresql:${POSTGRESQL_VERSION:-latest-slim}
restart: always
environment:
POSTGRES_DB: hawkbit
POSTGRES_USER: ${HAWKBIT_DB_USER:-postgres}
POSTGRES_PASSWORD: ${HAWKBIT_DB_PASSWORD:-postgres}
volumes:
- hawkbitdb-data:/var/lib/postgresql/data

hawkbit:
image: openremote/hawkbit-update-server:develop
restart: always
depends_on:
hawkbitdb:
condition: service_healthy
healthcheck:
interval: 3s
timeout: 3s
start_period: 3s
retries: 100
test: ["CMD", "curl", "--fail", "--silent", "http://localhost:8080/hawkbit"]
environment:
- SPRING_DATASOURCE_URL=jdbc:postgresql://hawkbitdb:5432/hawkbit
- SPRING_DATASOURCE_USERNAME=${HAWKBIT_DB_USER:-postgres}
- SPRING_DATASOURCE_PASSWORD=${HAWKBIT_DB_PASSWORD:-postgres}
- SPRING_JPA_DATABASE=POSTGRESQL
- SPRING_DATASOURCE_DRIVER_CLASS_NAME=org.postgresql.Driver
- HAWKBIT_DMF_RABBITMQ_ENABLED=false
- HAWKBIT_ARTIFACT_URL_PROTOCOLS_DOWNLOAD_HTTP_PORT=${HTTPS_FORWARDED_PORT:-443}
- HAWKBIT_ARTIFACT_URL_PROTOCOLS_DOWNLOAD_HTTP_PROTOCOL=https
- HAWKBIT_ARTIFACT_URL_PROTOCOLS_DOWNLOAD_HTTP_REF={protocol}://{hostnameRequest}:{port}$${server.servlet.context-path}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}
- HAWKBIT_SERVER_DDI_SECURITY_AUTHENTICATION_TARGETTOKEN_ENABLED=true
- SERVER_USE_FORWARD_HEADERS=true
- SERVER_FORWARD_HEADERS_STRATEGY=NATIVE
- SERVER_SERVLET_CONTEXT_PATH=/hawkbit
- HAWKBIT_SECURITY_USER_ADMIN_TENANT=#{null}
- HAWKBIT_SECURITY_USER_ADMIN_PASSWORD=#{null}
- HAWKBIT_SECURITY_USER_ADMIN_ROLES=#{null}
- HAWKBIT_SECURITY_USER_HAWKBIT_TENANT=DEFAULT
- HAWKBIT_SECURITY_USER_HAWKBIT_PASSWORD={noop}${HAWKBIT_PASSWORD:?HAWKBIT_PASSWORD must be set}
- HAWKBIT_SECURITY_USER_HAWKBIT_ROLES=TENANT_ADMIN
volumes:
- hawkbit-artifact-data:/opt/hawkbit/artifactrepo
expose:
- "8080"
```

Set the matching Manager environment variables:

```env
HAWKBIT_MANAGEMENT_API_URL=http://hawkbit:8080/hawkbit/rest/v1
HAWKBIT_PASSWORD=<same value as the hawkBit service>
```

### HAProxy Example

When hawkBit is behind the OpenRemote proxy, only expose the DDI controller API publicly. OpenRemote Manager uses the Management API over the internal Docker network.

Add the hawkBit ACLs in the `https` frontend before the final `use_backend manager_backend` rule:

```haproxy
# hawkBit DDI API for device polling and artifact downloads
acl hawkbit_management_api path_beg /hawkbit/rest/
acl hawkbit_ddi path_reg ^/hawkbit/[^/]+/controller/v1(/|$)

http-request deny deny_status 404 if hawkbit_management_api
use_backend hawkbit_backend if hawkbit_ddi
```

Add the hawkBit backend:

```haproxy
backend hawkbit_backend
server hawkbit "${HAWKBIT_HOST}":"${HAWKBIT_PORT}" resolvers docker_resolver
```

With the Compose example above, set the proxy environment variables:

```env
HAWKBIT_HOST=hawkbit
HAWKBIT_PORT=8080
```

The public DDI URL uses `/hawkbit/{tenant}/controller/v1/...`. The Management API at `/hawkbit/rest/v1/...` stays internal.

## Asset Usage

### Firmware Targets

To sync an OpenRemote asset as a hawkBit target, add the `firmwareTarget` meta item to one attribute of the asset.

When the asset is created or updated in the configured realm, the extension creates a hawkBit target using the OpenRemote asset ID as the hawkBit controller ID. The target info attribute is updated with the `controllerId` and `securityToken` returned by hawkBit.

When the asset is deleted, the matching hawkBit target is deleted.

### Firmware Metadata

To sync an attribute value as hawkBit target metadata, add the `firmwareMetadata` meta item to the attribute.

Metadata values are converted to strings. The OpenRemote attribute name is used as the hawkBit metadata key. Deleting the attribute removes the metadata entry in hawkBit.

The parent asset must also be synced as a firmware target with `firmwareTarget`.

## Firmware API

OpenRemote Manager exposes endpoints that forward to the configured hawkBit instance.

| Resource | Path | Functionality |
|---|---|---|
| Firmware targets | `firmware/target` | List targets, get target details, metadata, assigned and installed distribution sets, actions and action status messages |
| Software module types | `firmware/softwaremoduletype` | Create, list, get and delete software module types |
| Software modules | `firmware/softwaremodule` | Create, list, get and delete software modules, list and upload artifacts |
| Distribution set types | `firmware/distributionsettype` | Create, list, get and delete distribution set types, list module type assignments |
| Distribution sets | `firmware/distributionset` | Create, list, get, assign to targets and delete distribution sets |
| Target filters | `firmware/targetfilter` | Create, list, get and delete target filters, manage auto assignment |
| Rollouts | `firmware/rollout` | Create, list, get, start, pause and delete rollouts, list rollout groups |

Read endpoints require the OpenRemote read admin role. Write endpoints require the write admin role.

## Data Flow

```mermaid
sequenceDiagram
participant OR as OpenRemote
participant HB as hawkBit
participant Device as Device

OR->>HB: Create target from asset
HB-->>OR: Target controller ID and security token
OR->>HB: Sync selected asset attributes as metadata
OR->>HB: Create modules, distribution sets or rollouts
Device->>HB: Poll for assigned firmware actions
```

## Components

```
manager/
HawkbitFirmwareService.java Starts the integration, syncs assets and registers API resources
HawkbitResponseProxy.java Proxies hawkBit client responses for OpenRemote APIs

manager/resource/
TargetResourceImpl.java Proxies target and action requests to hawkBit
SoftwareModuleResourceImpl.java Proxies software module requests and artifact uploads
DistributionSetResourceImpl.java Proxies distribution set requests and target assignment
DistributionSetTypeResourceImpl.java Proxies distribution set type requests
SoftwareModuleTypeResourceImpl.java Proxies software module type requests
TargetFilterResourceImpl.java Proxies target filter requests
RolloutResourceImpl.java Proxies rollout requests

manager/hawkbit/
HawkbitTargetsClient.java RESTEasy client proxy for hawkBit targets
HawkbitSoftwareModulesClient.java RESTEasy client proxy for software modules
HawkbitDistributionSetsClient.java RESTEasy client proxy for distribution sets
HawkbitDistributionSetTypesClient.java RESTEasy client proxy for distribution set types
HawkbitSoftwareModuleTypesClient.java RESTEasy client proxy for software module types
HawkbitTargetFiltersClient.java RESTEasy client proxy for target filters
HawkbitRolloutsClient.java RESTEasy client proxy for rollouts
HawkbitBasicAuth.java Builds hawkBit basic auth headers
HawkbitMediaType.java Defines hawkBit media types

model/
FirmwareMetaItemType.java Defines firmwareTarget and firmwareMetadata meta items
FirmwareModelProvider.java Registers firmware meta items in the OpenRemote model

model/resource/
TargetResource.java Defines OpenRemote firmware target endpoints
SoftwareModuleResource.java Defines OpenRemote firmware software module endpoints
DistributionSetResource.java Defines OpenRemote firmware distribution set endpoints

model/hawkbit/
hawkBit API payload records used by the RESTEasy clients and firmware resources
```
88 changes: 88 additions & 0 deletions hawkbit/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
apply plugin: "groovy"
apply plugin: "java-library"
apply plugin: "maven-publish"
apply plugin: "signing"

base {
archivesName = "openremote-${project.name}-extension"
}

dependencies {
api "io.openremote:openremote-manager:$openremoteVersion"
api "io.openremote:openremote-model:$openremoteVersion"

api "org.jboss.resteasy:resteasy-client-api:$resteasyVersion"
api "org.jboss.resteasy:resteasy-jaxb-provider:$resteasyVersion"
api "org.jboss.resteasy:resteasy-multipart-provider:$resteasyVersion"

testImplementation "io.openremote:openremote-test:$openremoteVersion"
}

jar {
from sourceSets.main.allJava
}

javadoc {
failOnError = false
}

java {
withJavadocJar()
withSourcesJar()
}

publishing {
publications {
maven(MavenPublication) {
group = "io.openremote.extension"
artifactId = "openremote-${project.name}-extension"
from components.java
pom {
name = 'OpenRemote hawkBit extension'
description = 'Adds the hawkBit extension'
url = 'https://github.com/openremote/extensions'
licenses {
license {
name = 'GNU Affero General Public License v3.0'
url = 'https://www.gnu.org/licenses/agpl-3.0.en.html'
}
}
developers {
developer {
id = 'developers'
name = 'Developers'
email = 'developers@openremote.io'
organization = 'OpenRemote'
organizationUrl = 'https://openremote.io'
}
}
scm {
connection = 'scm:git:git://github.com/openremote/extensions.git'
developerConnection = 'scm:git:ssh://github.com:openremote/extensions.git'
url = 'https://github.com/openremote/extensions/tree/main'
}
}
}
}

repositories {
maven {
if (!version.endsWith('-LOCAL')) {
credentials {
username = findProperty("publishUsername")
password = findProperty("publishPassword")
}
}
url = version.endsWith('-LOCAL') ? layout.buildDirectory.dir('repo') : version.endsWith('-SNAPSHOT') ? findProperty("snapshotsRepoUrl") : findProperty("releasesRepoUrl")
}
}
}

signing {
def signingKey = findProperty("signingKey")
def signingPassword = findProperty("signingPassword")
if (signingKey && signingPassword) {
useInMemoryPgpKeys(signingKey, signingPassword)
sign publishing.publications.maven
}
}
Loading
Loading