Development setup
Prerequisites
| Tool | Version | Purpose |
|---|---|---|
| Go | 1.26+ | Backends, agent, connector, shared packages |
| Node.js | 24 LTS + pnpm 10 | Web apps, @entrosity/ui, this documentation site |
| Docker + Compose | recent | Postgres, MinIO, mailpit, Samba AD (tests) |
| sqlc | 1.31 (pinned) | Query codegen; make gen runs it with go run (needs a C compiler for cgo) |
| oapi-codegen | v2 (pinned) | API codegen; also run by make gen |
| golangci-lint | v2.13+ | Lint (brew install golangci-lint) |
| WiX Toolset | v4 (Windows only) | MSI builds |
| A Windows 10/11 VM | – | Run the agent and connector for real (UTM, Parallels, VMware) |
Clone the repositories
The code lives in separate repositories. Clone the ones you need side by side in one folder; the scripts and Makefiles find each other there:
mkdir entrosity && cd entrosity
for r in infra shared-go ui axis.backend axis.frontend axis-agent axis-connector hub.backend hub.frontend docs; do
gh repo clone "entrosity/entrosity-$r"
done
export GOPRIVATE=github.com/entrosity # private Go modules
gh auth setup-git # Git credentials for go get
echo "//npm.pkg.github.com/:_authToken=<token with read:packages>" >> ~/.npmrc
Every Go repository is a single module: go build ./... and go test ./...
work at its root, and make help lists its targets.
First run
Users sign in on Entrosity Hub, so a development stack runs the Hub next to Axis: four processes, each in its own terminal.
cd entrosity-infra
cp deploy/.env.example deploy/.env # edit secrets if needed
make dev-up # postgres, minio (+ bucket), mailpit
docker compose -f deploy/docker-compose.yml exec postgres createdb -U rmm platform # the Hub's database (once)
cd ../entrosity-hub.backend
make migrate # the Hub's migrations
make bootstrap-admin [email protected] # type the password (12+ chars), Enter
make dev # the Hub's API on :8090 (internal API :8091)
cd ../entrosity-axis.backend
make migrate # Axis: river + app migrations
make dev # Axis's API on :8080 (curl localhost:8080/healthz)
cd ../entrosity-axis.frontend && pnpm install && pnpm dev # Axis's Vite server on :5173 (base /axis/)
cd ../entrosity-hub.frontend && pnpm install && pnpm dev # the Hub's web app on :5175
The backends read ../entrosity-infra/deploy/.env (override with
ENV_FILE=…).
The dev stack runs PostgreSQL 18. A postgres-data volume left over from the
monorepo's PostgreSQL 16 stack will not start: remove it
(docker compose -f deploy/docker-compose.yml down -v in entrosity-infra)
and migrate again.
Open http://localhost:5175: the Hub's dev server serves one origin
like production, with the Hub at /, its API /api/platform (to :8090),
Axis at /axis (to the Axis Vite server) and Axis's API at /axis/api
(to :8080, prefix stripped like Caddy). Sign in as the admin, create an
organization with Entrosity Axis enabled, and open Axis from the
product list; Axis picks up the organization within its sync interval.
deploy/.env.example connects the two: RMM_PLATFORM_URL=http://localhost:5175
(the token issuer), RMM_PLATFORM_INTERNAL_URL=http://localhost:8091,
RMM_PLATFORM_TOKEN equal to the rmm entry of PLATFORM_PRODUCT_TOKENS,
and RMM_PORTAL_URL=http://localhost:5175/axis. E-mails (the Hub's
invitations and password resets, Axis's alerts) land in mailpit at
http://localhost:8025.
Non-interactive admin creation (in entrosity-hub.backend):
go run ./cmd/server bootstrap-admin --email [email protected] --password-stdin <<<'long passphrase'
Every variable is explained in the Configuration reference.
Row-level security in development
RLS applies in development too. The pool connects as the owner rmm and
switches to rmm_app, and sets the scope from the context before each use
of a connection (Multi-tenancy). For
ad-hoc SQL as the application: SET ROLE rmm_app; SET app.tenant_id = '<uuid>';.
Packages and object storage
In dev mode the backend creates the bucket at start. The portal uploads
package files straight to MinIO with a presigned PUT, so the MinIO
endpoint must be reachable from the browser (the compose MinIO allows any
origin). Set RMM_WINGET_SOURCE_URL=off when working offline.
Changing shared code
@entrosity/ui: inentrosity-ui,pnpm build && pnpm pack, then in an apppnpm add ../entrosity-ui/entrosity-ui-<version>.tgzto try it (do not commit that). Release by taggingentrosity-ui; the apps are bumped automatically.entrosity-shared-go:go work init . ../entrosity-shared-goin the consumer (the file is git-ignored). Release by tagging, thengo get github.com/entrosity/[email protected]in every consumer.- An API contract: change
api/openapi.yamlin the backend and runmake gen; in the app,OPENAPI_SPEC=../entrosity-axis.backend/api/openapi.yaml pnpm fetch:api && pnpm gen:api(or…hub…). After the backend change reachesmain, the app'sopenapi-bumpworkflow does the same.
Self-update releases locally
- In
entrosity-axis.backend,make release-keygenprintsRMM_RELEASE_SIGNING_KEY=…(for the dev.env) andRELEASE_PUBLIC_KEY=…(for builds). - Build with the public key:
RELEASE_PUBLIC_KEY=<public> make buildinentrosity-axis-agentandentrosity-axis-connector. - Publish in the portal (Admin → Agent releases) or, in
entrosity-axis.backend,make publish-release api=http://localhost:8080 file=../entrosity-axis-agent/dist/rmm-agent.msi version=… [product=connector] [channel=beta] [rollout=20](RMM_RELEASE_TOKENset in both environments).
Simulated agents accept updates too: they "install" by reconnecting with the new version.
Running the agent against a dev server
- In the portal, create an agent enrollment token.
- Make the dev server reachable from the VM: the VM's host-gateway
address (
http://10.0.2.2:8080on UTM/QEMU NAT), orngrok http 8080/ Tailscale. SetRMM_PUBLIC_URLaccordingly so download URLs work. make buildinentrosity-axis-agent(ormake msion Windows). For fast iteration run the binary in the console instead of installing the service:.\rmm-agent.exe run --server http://10.0.2.2:8080 --token <token>- The device appears under Devices within seconds. Logs:
%ProgramData%\RMM\logs\agent.log.
Simulated agents (no Windows needed)
In entrosity-axis.backend:
make sim token=<enrollment token> count=200
go run ./cmd/simagent --help
Fake agents enroll with unique identities, heartbeat, send canned
inventories and execute jobs with fake results after 1–5 s. Flags include
--failure-rate, --job-min, --job-max, --heartbeat,
--heartbeat-jitter, --prefix, --offset, --ramp and
--inventory-size. Enrollment is rate-limited per IP; the tool retries
429s, or raise RMM_ENROLL_RATE_PER_MINUTE.
Running the connector
The LDAP part runs on macOS and Linux against the Samba AD test domain
(in entrosity-axis-connector):
docker compose -f test/samba/docker-compose.yml up -d --wait samba # rmm.test, LDAPS on :10636
docker compose -f test/samba/docker-compose.yml exec -T samba bash /seed.sh # 50 computers in 4 OUs
# portal: Active Directory → Download connector → copy the token
RMM_CONNECTOR_DATA_DIR=/tmp/rmm-connector go run ./cmd/rmm-connector run \
--server http://localhost:8080 --token <connector token>
In the wizard use host 127.0.0.1, port 10636, LDAPS with Skip
certificate verification, base DN DC=rmm,DC=test, bind
CN=svc-rmm,CN=Users,DC=rmm,DC=test / Svc-Pa55word!.
Pushes need real Windows targets with SMB (ADMIN$) or WinRM enabled and a
push account that is a local administrator there. Without object storage, set
RMM_AGENT_MSI_URL to a reachable agent MSI.
make simconnector token=… [computers=200] in entrosity-axis.backend
runs a simulated connector with a generated directory (no LDAP, no
Windows).
Documentation site
In entrosity-docs:
pnpm install
pnpm start # http://localhost:3000, live reload
pnpm build # static site in build/; fails on broken links
See Writing docs.