Skip to content
Merged
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
52 changes: 52 additions & 0 deletions .github/steps/rust-setup/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Prepare Rust tools
description: Set up Rust toolchain and Cargo cache
inputs:
path:
description: Rust workspace path
required: true
toolchain:
description: Rust toolchain (e.g. stable, 1.79.0, nightly)
required: false
default: stable
targets:
description: Comma-separated targets to add (e.g. x86_64-unknown-linux-gnu,x86_64-pc-windows-msvc)
required: false
default: ""
components:
description: Comma-separated components (e.g. clippy, rustfmt)
required: false
default: ""
shared-key:
description: Shared key to cache
required: false
default: ""

runs:
using: "composite"
steps:
# Toolchain install + targets/components
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ inputs.toolchain }}
targets: ${{ inputs.targets }}
components: ${{ inputs.components }}

# Enhanced Rust caching
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: ${{ inputs.path }}
prefix-key: "v1-rust"
shared-key: ${{ inputs.shared-key }}
cache-targets: true
cache-on-failure: true

- name: Add Rust targets, if needed
shell: bash
run: |
IFS=',' read -ra ARCHITECTURES <<< "${{ inputs.targets }}"
for RUST_TARGET in "${ARCHITECTURES[@]}"; do
RUST_TARGET=$(echo "$RUST_TARGET" | tr -d '[:space:]')
rustup target list --installed | grep -q "^${RUST_TARGET}$" || (echo "Adding $RUST_TARGET" && rustup target add "$RUST_TARGET")
done
6 changes: 6 additions & 0 deletions .github/workflows/changes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ on:
value: ${{ jobs.changes.outputs.java }}
openframe-frontend-core:
value: ${{ jobs.changes.outputs.openframe-frontend-core }}
rust:
value: ${{ jobs.changes.outputs.rust }}


jobs:
Expand All @@ -16,6 +18,7 @@ jobs:
outputs:
java: ${{ steps.filter.outputs.java }}
openframe-frontend-core: ${{ steps.filter.outputs.openframe-frontend-core }}
rust: ${{ steps.filter.outputs.rust }}
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -59,3 +62,6 @@ jobs:
- '.mvn/**'
openframe-frontend-core:
- 'openframe-frontend-core/**'
rust:
- 'clients/**'
- '.github/steps/rust-setup/**'
56 changes: 56 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ on:
options:
- java
- node
- rust
version:
description: 'Version to release OSS Libs (x.y.z). Leave empty for auto patch bump.'
required: false
Expand Down Expand Up @@ -223,3 +224,58 @@ jobs:
run: npm publish --tag latest --access public
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}


release-rust:
name: Release Rust
runs-on: ubuntu-latest
permissions:
contents: write
if: github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'rust'

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Get Previous Version
run: |
OLD_VERSION=$(git tag --list 'v*' | sed 's/^v//' | \
grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1)
OLD_VERSION=${OLD_VERSION:-0.0.0}
echo "OLD_VERSION=$OLD_VERSION" >> $GITHUB_ENV
echo "Found old version: $OLD_VERSION"

- name: Set Release Version
env:
NEW_VERSION: ${{ github.event.inputs.version }}
run: |
OLD_BASE="${{ env.OLD_VERSION }}"
[[ "$OLD_BASE" =~ ^([0-9]+\.[0-9]+\.[0-9]+) ]] && OLD_BASE="${BASH_REMATCH[1]}"
IFS='.' read -r old_major old_minor old_patch <<< "$OLD_BASE"

if [ -z "$NEW_VERSION" ]; then
NEW_VERSION="${old_major}.${old_minor}.$((old_patch + 1))"
echo "No version provided, auto patch bump: $OLD_BASE → $NEW_VERSION"
else
IFS='.' read -r new_major new_minor new_patch <<< "$NEW_VERSION"
if ! ([ "$new_major" -eq $((old_major + 1)) ] && [ "$new_minor" -eq 0 ] && [ "$new_patch" -eq 0 ]) &&
! ([ "$new_major" -eq "$old_major" ] && [ "$new_minor" -eq $((old_minor + 1)) ] && [ "$new_patch" -eq 0 ]) &&
! ([ "$new_major" -eq "$old_major" ] && [ "$new_minor" -eq "$old_minor" ] && [ "$new_patch" -eq $((old_patch + 1)) ]); then
echo "Invalid version bump: $OLD_BASE → $NEW_VERSION"
exit 1
fi
echo "New version validated: $OLD_BASE → $NEW_VERSION"
fi
echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV

- name: Create and Push Tag
run: |
TAG="v${{ env.NEW_VERSION }}"
if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
echo "Tag $TAG already exists — aborting."
exit 1
fi
git tag "$TAG"
git push origin "$TAG"
echo "Created and pushed $TAG"
38 changes: 37 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,47 @@ jobs:
path: ${{ matrix.path }}/dist/
retention-days: 1
if-no-files-found: error


test-rust:
name: "Test Rust (${{ matrix.os }})"
needs: changes
if: needs.changes.outputs.rust == 'true'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest]
defaults:
run:
shell: bash
env:
OPENFRAME_VERSION: 0.0.0-dev
CARGO_INCREMENTAL: 0

steps:
- uses: actions/checkout@v4

- name: Prepare Rust tools
uses: ./.github/steps/rust-setup
with:
path: clients/openframe-client
components: clippy,rustfmt
shared-key: openframe-client-${{ matrix.os }}

- name: Lint (cargo fmt --check + cargo clippy -D warnings)
run: make -C clients/openframe-client lint

- name: Build client binary
run: make -C clients/openframe-client build-bin

- name: Test
run: make -C clients/openframe-client test


all-checks:
name: "All Checks"
needs: [test-java, test-node]
needs: [test-java, test-node, test-rust]
runs-on: ubuntu-latest
if: always()
steps:
Expand Down
15 changes: 15 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,21 @@ mvn install -DskipTests

## Code Style and Conventions

### Rust (agent client)

The Rust OpenFrame agent lives under [`clients/`](clients/README.md) as the
`openframe-agent-lib` crate. Formatting and lint are **required** and enforced
both by committed git hooks and by CI:

```bash
make -C clients setup-hooks # run once: install the fmt/clippy git hooks
make -C clients/openframe-client lint # the same gate, on demand
```

Clippy policy is "strict but sane" (`clippy::all` warns; `correctness` and
`suspicious` are denied), with any warning failing under `-D warnings`. See
[`clients/README.md`](clients/README.md) for the full workflow.

### Java Style

| Convention | Rule |
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ mvn verify -pl openframe-data-mongo-sync
| `openframe-external-api-service-core` | External REST API for integrations |
| `sdk/fleetmdm` | Fleet MDM Java SDK |
| `sdk/tacticalrmm` | Tactical RMM Java SDK |
| `clients/openframe-client` | Rust cross-platform agent (`openframe-agent-lib`) — see [clients/README.md](clients/README.md) |

---

Expand Down
17 changes: 17 additions & 0 deletions clients/.githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
# OpenFrame Rust client: verify formatting before committing.
# Only fires when staged changes touch Rust sources under clients/.
set -euo pipefail

if ! git diff --cached --name-only | grep -qE '^clients/.*\.rs$'; then
exit 0
fi

if ! command -v cargo >/dev/null 2>&1; then
echo "[pre-commit] cargo not found; skipping Rust fmt check." >&2
exit 0
fi

cd "$(git rev-parse --show-toplevel)/clients/openframe-client"
echo "[pre-commit] cargo fmt --all -- --check"
cargo fmt --all -- --check
33 changes: 33 additions & 0 deletions clients/.githooks/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# OpenFrame Rust client: run clippy (deny warnings) before pushing.
# Only fires when the pushed commits touch the Rust client; skips gracefully
# when cargo is unavailable so non-Rust contributors are not blocked.
set -euo pipefail

root="$(git rev-parse --show-toplevel)"
zero="0000000000000000000000000000000000000000"

# Collect files changed in the commits being pushed (reads refs on stdin).
changed=""
while read -r _local_ref local_oid _remote_ref remote_oid; do
[ "$local_oid" = "$zero" ] && continue
if [ "$remote_oid" = "$zero" ]; then
base="$(git merge-base origin/main "$local_oid" 2>/dev/null || echo "$local_oid")"
else
base="$remote_oid"
fi
changed+="$(git diff --name-only "$base".."$local_oid" -- 'clients/' 2>/dev/null || true)"$'\n'
done

if ! echo "$changed" | grep -qE '\.(rs|toml)$'; then
exit 0
fi

if ! command -v cargo >/dev/null 2>&1; then
echo "[pre-push] cargo not found; skipping Rust clippy check." >&2
exit 0
fi

cd "$root/clients/openframe-client"
echo "[pre-push] cargo clippy --all-targets -- -D warnings"
OPENFRAME_VERSION="${OPENFRAME_VERSION:-0.0.0-dev}" cargo clippy --all-targets -- -D warnings
6 changes: 6 additions & 0 deletions clients/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Install the git hooks for the Rust client (fmt on pre-commit, clippy on pre-push).
# Run once after cloning: make -C clients setup-hooks
.PHONY: setup-hooks
setup-hooks:
git config core.hooksPath "$$(git rev-parse --show-toplevel)/clients/.githooks"
@echo "Git hooks enabled: pre-commit = cargo fmt --check, pre-push = cargo clippy -D warnings."
79 changes: 79 additions & 0 deletions clients/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# OpenFrame Rust client (`openframe-agent-lib`)

This directory holds the cross-platform OpenFrame agent, published as the
standalone library crate **`openframe-agent-lib`** (crate/lib name `openframe`).
The tenant repos (`openframe-oss-tenant`, `openframe-saas-tenant`) depend on this
library and ship only a thin binary that calls `openframe::run()`.

Each crate under `clients/` is standalone (its own `Cargo.toml`, `Cargo.lock`,
lints and build `Makefile`), so a future `clients/openframe-chat` is fully
independent. Shared, repo-level tooling stays here at `clients/`.

```
clients/
├── Makefile # hook setup only: `make -C clients setup-hooks`
├── rustfmt.toml # formatting config (applies to every crate below it)
├── .githooks/ # pre-commit (fmt) + pre-push (clippy)
└── openframe-client/ # the standalone library crate (openframe-agent-lib)
├── Cargo.toml # incl. [lints.clippy] policy
├── Cargo.lock
└── Makefile # fmt / fmt-check / clippy / lint / build / test
```

## One-time setup

```bash
# from the repo root
make -C clients setup-hooks
```

This enables the committed git hooks:

- **pre-commit** → `cargo fmt --all -- --check` (fails on unformatted Rust).
- **pre-push** → `cargo clippy --all-targets -- -D warnings`.

Both hooks only fire when the change touches the Rust client, and skip cleanly
if `cargo` is not installed, so they do not get in the way of Java-only work.
CI (`.github/workflows/rust-client.yml`) runs the same gate authoritatively.

Requires a stable Rust toolchain (`rustup` recommended) with the `rustfmt` and
`clippy` components.

## Common commands

Dev commands live in the crate's own Makefile — run from
`clients/openframe-client` (or `make -C clients/openframe-client <target>`).
`OPENFRAME_VERSION` is injected at build time (defaults to `0.0.0-dev`).

```bash
make fmt # format
make lint # fmt --check + clippy -D warnings (the required gate)
make build # release build of the library
make test # build + test
```

The binary is **off by default** — it is gated behind the `bin` cargo feature so
the crate stays lean for library consumers. To build/run it directly here:

```bash
OPENFRAME_VERSION=0.0.0-dev cargo build --features bin --bin openframe-client
OPENFRAME_VERSION=0.0.0-dev cargo run --features bin --bin openframe-client -- --help
```

## Consuming the library from a tenant repo

Tenant bin crates depend on this library via a git dependency pinned to a
release tag:

```toml
[dependencies]
openframe-agent-lib = { git = "https://github.com/flamingo-stack/openframe-oss-lib.git", tag = "v0.1.0" }
```

Their `main.rs` is simply:

```rust
fn main() -> anyhow::Result<()> {
openframe::run()
}
```
6 changes: 6 additions & 0 deletions clients/openframe-client/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Cargo configuration for OpenFrame client
# Fix C++ linker issues on Windows by statically linking the Visual C++ Runtime

[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static"]

Loading
Loading