Skip to content

Repository files navigation

RustERP by NDX Pty Ltd

Official site: https://RustERP.biz

RustERP is a modular, snug-fit, open-source ERP written in Rust for small-to-medium businesses. Users should feel the immediacy of classic desktop apps; implementors should enable only what each business needs so the product stays cognitively efficient for end users.

This repository is the open-source project source. Product information, documentation, and project news live on the official site.

Source & community

Website RustERP.biz
Source github.com/ndx-au/RustERP
Contributing CONTRIBUTING.md

Architecture decisions

Decision Choice
License Apache-2.0 (see LICENSE and NOTICE)
Tenancy Single-tenant by design; self-host with LXC/container isolation preferred
API API-first / headless — gRPC + protobufs; UIs are separate consumers
Storage PostgreSQL via sqlx (connection pool, migrations, health ping)
Permissions Classic RBAC (Users / Groups / Roles / resource:action); SSO (OIDC) later
Modularity “Snug fit” — consultants activate only the functional domains a business needs

Functional domains vs technical modules

  • Functional domains — user-facing capabilities that can be activated per deployment (MVP intent: always-on Core Platform, Parties, Catalog, Sales, Payments & Banking, toggleable Inventory). Owned conceptually by the module registry (rusterp-modules).
  • Technical modules — code organization (platform crates such as storage, future auth/RBAC/events). Not end-user toggles.

Purchasing, full double-entry GL, advanced CRM, projects, HR, and manufacturing are out of MVP scope.

Repository layout

Cargo.toml                 workspace root
rusterp-server.toml        optional server config (copy from rusterp-server.toml.example)
install.sh                 bootstrap `rusterp` helper CLI only
dist/
  deploy-ui-stack.sh       build UI WASM + install to dist/ui/ + run server
  ui/                      static WASM shell (built artifact; not hand-edited)
  rusterp, rusterp-lib.sh  installer helper CLI
  test-dist.sh             offline installer smoke
crates/
  rusterp-storage/         PostgreSQL storage (sqlx pool + migrations)
  rusterp-modules/         functional module registry / activation skeleton
  rusterp-parties/         Parties domain (first functional domain)
  rusterp-proto/           tonic/prost codegen from proto/
  rusterp-server/          dual-transport server (TCP gRPC + HTTP/slozhn)
    src/config.rs          optional TOML config loader
    src/http.rs            axum static shell + slozhn WebSocket at /rpc
    src/port_guard.rs      listen-port conflict policy (default: clobber)
proto/                     gRPC/protobuf conventions and .proto defs
.local/                    runtime pid/log (gitignored; created by deploy)

Domain crates under crates/: rusterp-parties, rusterp-catalog, rusterp-sales, rusterp-payments, rusterp-inventory, and rusterp-auth (plus rusterp-modules registry). Production repositories are PostgreSQL-backed; parties also has an in-memory repository for unit tests. See the product roadmap.

Reference UI lives in a separate repository: RustERP-UI-WASM (egui/eframe WASM + native client). It consumes the core over slozhn gRPC-over-WebSocket at /rpc — never embed UI code in this repo.

gRPC: rusterp-server exposes Party, Catalog, Sales, Payment, Inventory, Module, Auth, and Health services (shared on TCP and slozhn). Soft RBAC when RUSTERP_AUTH_ENFORCE=1 (metadata x-rusterp-user). Generated types live in rusterp-proto (sources under proto/). Schema reference: docs/schema.md.

Dual transport (Macaron-aligned)

rusterp-server runs two listeners in one process (shared Postgres-backed state):

Browser / WASM UI ──► HTTP :8123 ──► GET /          static WASM (dist/ui/)
                                    WS  /rpc         slozhn gRPC-over-WebSocket

API tools / grpcurl ──► TCP :50051 ──► native gRPC + server reflection
Leg Default Use
TCP gRPC 127.0.0.1:50051 (RUSTERP_LISTEN) grpcurl, integrations, reverse-proxy h2c (e.g. Caddy)
HTTP + slozhn WS 127.0.0.1:8123 (RUSTERP_HTTP_LISTEN) Browser/native UI at ws://host/rpc; optional static WASM from dist/ui/

Port 8123 is the deliberate HTTP default (avoids common dev ports like :8080). Same-origin WASM uses page-relative /rpc; off-origin clients use ws://127.0.0.1:8123/rpc.

Protobuf: proto/rusterp/party/v1/ (rusterp.party.v1), proto/rusterp/platform/v1/ (rusterp.platform.v1).

Development

Requires a stable Rust toolchain and protoc (Protocol Buffers compiler) on PATH for rusterp-proto codegen.

cargo check
cargo test

No external database is required. Default tests do not need a running server.

Run the server

cargo run -p rusterp-server
# override listen (CLI wins over config/env):
cargo run -p rusterp-server -- --listen 127.0.0.1:50051 --http-listen 127.0.0.1:8123
RUSTERP_LISTEN=127.0.0.1:50052 RUSTERP_HTTP_LISTEN=127.0.0.1:8123 cargo run -p rusterp-server

Configuration precedence (highest wins): CLI flags → rusterp-server.toml (or path in RUSTERP_CONFIG) → RUSTERP_* env vars → built-in defaults.

Copy rusterp-server.toml.example to rusterp-server.toml to customize listen addresses, static WASM directory, and port-conflict policy. Example:

[tcp]
listen = "0.0.0.0:50051"

[http]
listen = "0.0.0.0:8123"
static_dir = "dist/ui"

[port_conflict]
policy = "clobber"   # default: restart self via pidfile, then free the port
pid_file = ".local/rusterp-server.pid"
graceful_secs = 5
Variable Default Purpose
RUSTERP_CONFIG rusterp-server.toml in cwd / repo root Path to server TOML
RUSTERP_HOME cwd Prefix for relative pid_file paths
RUSTERP_LISTEN 127.0.0.1:50051 TCP gRPC (grpcurl, API tools)
RUSTERP_HTTP_LISTEN 127.0.0.1:8123 HTTP + gRPC-over-WebSocket at /rpc
RUSTERP_STATIC from config or dist/ui/ when present Static WASM shell directory
RUSTERP_POSTGRES_URL (required) PostgreSQL connection URI
RUSTERP_PG_MAX_CONNECTIONS 20 sqlx pool max connections
RUSTERP_PG_MIN_CONNECTIONS 2 sqlx pool min warm connections
RUSTERP_PG_ACQUIRE_TIMEOUT_SECS 3 Pool acquire timeout (seconds)

On startup, if a listen port is busy the server applies [port_conflict]: clobber (default) sends SIGTERM to the prior instance recorded in the pidfile, waits, then kills any remaining occupant; fail exits with an error.

Server reflection is enabled on the TCP leg for discovery tools. Both listeners shut down together on Ctrl+C / SIGTERM.

Storage (PostgreSQL)

rusterp-server requires a PostgreSQL connection URI via RUSTERP_POSTGRES_URL or [storage].postgres_url in rusterp-server.toml. Persistence uses sqlx with an explicitly tuned connection pool and embedded migrations applied at startup.

Setting Default Purpose
max_connections 20 Pool size cap
min_connections 2 Warm connections kept open
acquire_timeout_secs 3 Fail fast when pool is exhausted
idle_timeout_secs 600 Recycle idle connections
max_lifetime_secs 1800 Recycle connections before LB/firewall drops

Health checks call SELECT 1 on the pool. Local dev and tests that need a real database require a running PostgreSQL instance; unit tests use in-memory repositories and skip integration tests when RUSTERP_POSTGRES_URL is unset.

Reference UI (separate repo)

The egui WASM/native shell is RustERP-UI-WASM. It talks to this core over slozhn at /rpc (default ws://127.0.0.1:8123/rpc for native dev). Privacy browsers may block WebGL/canvas until the user allows it via the address-bar permission icon — the UI shows a Macaron-style troubleshooting card when graphics init fails.

Deploy UI stack (WASM + server)

From this repo root, with a sibling checkout of RustERP-UI-WASM (or set RUSTERP_UI_ROOT):

./dist/deploy-ui-stack.sh          # trunk build + rsync to dist/ui/ + foreground server
./dist/deploy-ui-stack.sh --bg     # same, server in background

Requires trunk, protoc, and wasm32-unknown-unknown. The script builds the UI, copies artifacts to dist/ui/, builds rusterp-server release, and starts with RUSTERP_CONFIG (default ./rusterp-server.toml). Background mode logs to .local/rusterp-server.log.

Typical reverse-proxy layout (TLS terminates at the proxy; host listens on :8123 HTTP and :50051 TCP):

Public host Upstream Purpose
rusterp-ui.example host:8123 WASM shell + slozhn /rpc
rusterp-api.example host:50051 h2c TCP gRPC for API tools

Example grpcurl (optional manual check)

With grpcurl and the server running:

# list services (reflection)
grpcurl -plaintext 127.0.0.1:50051 list

# health
grpcurl -plaintext 127.0.0.1:50051 rusterp.platform.v1.HealthService/Check

# create a party
grpcurl -plaintext -d '{
  "display_name": "Acme Ltd",
  "roles": ["PARTY_ROLE_CUSTOMER"]
}' 127.0.0.1:50051 rusterp.party.v1.PartyService/CreateParty

# list parties
grpcurl -plaintext -d '{}' 127.0.0.1:50051 rusterp.party.v1.PartyService/ListParties

Regenerate protobuf Rust

cargo build -p rusterp-proto

See proto/README.md.

Install core (source bootstrap)

Self-host / LXC path for the core server (single-tenant). Source install — needs git, stable Rust (cargo), and protoc. Does not auto-install toolchains. The reference UI (RustERP-UI-WASM) is a separate checkout; use dist/deploy-ui-stack.sh or serve WASM into dist/ui/ manually.

1. Bootstrap the helper CLI

Linux and macOS. Installs only rusterp under a user-local prefix by default (~/.local/share/rusterp/cli + ~/.local/bin/rusterp). As root, defaults are /opt/rusterp and /usr/local/bin. Re-run is safe (idempotent copy/symlink).

# from a checkout:
./install.sh

# or remote bootstrap of the CLI only:
curl -fsSL https://raw.githubusercontent.com/ndx-au/RustERP/main/install.sh | bash

Ensure ~/.local/bin is on PATH if needed.

2. Install core from source

rusterp install core              # default git branch: dist
rusterp install core --branch dist
rusterp install core --branch my-pin
rusterp status
  • Default ref is dist. There is no silent fallback to main if the branch is missing or unfetchable (fail-closed).
  • Clone/build prefix: RUSTERP_HOME (default ~/.local/share/rusterp, or /opt/rusterp when root).
  • Builds rusterp-server in release mode and installs:
    • $RUSTERP_HOME/bin/rusterp-server
    • $RUSTERP_HOME/bin/run-core (wrapper honoring RUSTERP_LISTEN)
    • example env file and optional systemd unit template under $RUSTERP_HOME

3. Run core

# default listen 127.0.0.1:50051 (TCP gRPC only via run-core wrapper)
rusterp-run-core
# or run the server binary directly (both TCP + HTTP legs when configured):
"$HOME/.local/share/rusterp/bin/rusterp-server"
RUSTERP_CONFIG="$HOME/.local/share/rusterp/rusterp-server.toml" \
  RUSTERP_LISTEN=0.0.0.0:50051 \
  RUSTERP_HTTP_LISTEN=0.0.0.0:8123 \
  "$HOME/.local/share/rusterp/bin/rusterp-server"

The run-core wrapper honors RUSTERP_LISTEN for the TCP leg only. For the HTTP/slozhn leg and static WASM, run rusterp-server with rusterp-server.toml (see Run the server above) or set RUSTERP_HTTP_LISTEN.

Party and MVP domain data is persisted in PostgreSQL. Soft auth via RUSTERP_AUTH_ENFORCE=1 (default off). Point the separate RustERP-UI-WASM client or grpcurl at the listen addresses when ready. Set RUSTERP_POSTGRES_URL (or [storage].postgres_url in config).

Terraform-oriented environment

Variable Purpose
RUSTERP_HOME Install prefix (source tree, binaries, state)
RUSTERP_BIN_DIR Where install.sh places rusterp on PATH
RUSTERP_REPO_URL Git remote for install core (default GitHub origin)
RUSTERP_CONFIG Path to rusterp-server.toml (listen + port policy)
RUSTERP_LISTEN TCP gRPC listen (host:port, default 127.0.0.1:50051)
RUSTERP_HTTP_LISTEN HTTP + slozhn listen (host:port, default 127.0.0.1:8123)
RUSTERP_BOOTSTRAP_REF Ref used when install.sh downloads CLI files remotely (default main)
RUSTERP_UI_ROOT Sibling path to RustERP-UI-WASM for deploy-ui-stack.sh

No interactive prompts. Prefer env or an EnvironmentFile (see dist/rusterp-server.service.example).

Offline checks / manual smoke

# no network — syntax + default-branch / argv parsing
./dist/test-dist.sh

Manual smoke (not required in CI): clean LXC → ./install.shrusterp install corerusterp statusrun-core → Health via grpcurl.

Design notes (non-normative): DIST.md.

License

RustERP by NDX Pty Ltd is licensed under the Apache License, Version 2.0. See LICENSE and NOTICE.

Copyright 2026 NDX Pty Ltd and contributors.

You are free to use, modify, and redistribute RustERP — including in commercial and internal products — under those terms. Contributions are welcome under the same license; see CONTRIBUTING.md.

About

RustERP by NDX Pty Ltd — modular, snug-fit open-source ERP for SMBs

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages