MEMAR How-To Guide
Everything you need to run your fleet.
Get running in minutes.
Pick your install method. Docker is the fastest path; bare-metal is recommended for production.
# Clone and start
git clone https://github.com/stratum-industries-foss/memar
cd memar
cp .env.example .env
# Edit .env — set MEMAR_JWT_SECRET at minimum:
# MEMAR_JWT_SECRET=$(openssl rand -hex 32)
docker compose up -d
# Get the auto-generated admin password:
docker compose logs memar | grep -i password MEMAR listens on :8000 (mapped from internal :9000).
# Bare-metal / systemd — no Docker
cd ~/apps/memar/backend
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cd ../frontend && npm install && npm run build
# Set MEMAR_JWT_SECRET (required) in /etc/memar.env — SMTP and Stripe
# credentials are optional and only needed if you use password-reset email
# or the built-in billing routes.
sudo systemctl enable --now memar # your own unit file, pointing at
# 'uvicorn app.main:app' in backend/.venv The installer validates health at /api/healthz and auto-rolls back on failure.
# Backend
cd backend
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
MEMAR_JWT_SECRET=$(openssl rand -hex 32) \
uvicorn app.main:app --port 9000 --reload
# Frontend — separate terminal
cd frontend
npm install
npm run dev # dev server at http://localhost:5173 Backend at :9000, Vite dev server at :5173 with HMR.
First-run checklist
Open the UI
Navigate to http://localhost:8000 (or your configured MEMAR_PUBLIC_URL).
Log in as admin
Admin password is printed in container logs on first run. Username: admin (or MEMAR_ADMIN_USER).
Add your first device
Settings → Devices → Add device. See Adding Devices for the config format.
Activate your license
Free tier supports 5 devices. For more, see License Activation.
Adding devices.
Devices are defined in a YAML config file. The default path is config/devices.yaml — override with MEMAR_CONFIG_PATH.
# config/devices.yaml
printers:
- id: voron-1
name: "Voron 2.4"
protocol: moonraker
base_url: http://192.168.1.100:7125
api_key: null # optional — required for some protocols
machine_type: fdm
enabled: true
tags: [production, bay-1]
- id: my-cnc
name: "Shapeoko Pro"
protocol: grbl
base_url: tcp://192.168.1.101:23
machine_type: cnc_router
enabled: true Config fields
| Field | Type | Description |
|---|---|---|
| id | string | Unique device identifier — used in webhook payloads and API paths |
| name | string | Human-readable display name |
| protocol | string | Adapter protocol key — see Adapter Reference below |
| base_url | string | Device endpoint URL (http://, tcp://, or opc.tcp:// depending on adapter) |
| api_key | string | null | Authentication credential — adapter-specific; null if not required |
| machine_type | string | One of the machine types listed below |
| enabled | bool | Whether MEMAR polls this device; false = paused, not deleted |
| tags | string[] | Optional labels for grouping and filtering in the dashboard |
Valid machine_type values
18 protocol adapters — 6 proven, 12 alpha.
Each adapter translates machine-native protocols into MEMAR's unified state model. Set protocol: in your device config to the value in the card header. Proven adapters are battle-tested on real 3D-printer fleets; alpha adapters are real, working code not yet verified against every real machine.
Moonraker
| required | base_url |
| notes | api_key optional (X-Api-Key header) |
OctoPrint
| required | base_url, api_key |
| notes | X-Api-Key header |
Prusa Connect
| required | base_url, api_key |
| notes | Cloud API; base_url is your printer UUID endpoint. Requires internet — not air-gap compatible. |
Bambu Labs
| required | base_url, serial, api_key |
| notes | LAN mode required; api_key is 8-digit access code; MQTT TLS :8883. Unofficial, reverse-engineered protocol — can shift across Bambu firmware updates. |
Marlin / ESP3D
| required | base_url, api_key |
| notes | HTTP gcode bridge; api_key: esp3d or repetier:{token} |
Duet / RRF
| required | base_url |
| notes | RepRap firmware HTTP API |
FluidNC
| required | base_url |
| notes | HTTP gcode bridge |
Generic MQTT
| required | mqtt_topic, mqtt_field_mapping |
| notes | Custom topic schema for any MQTT-capable device — you write the field mapping |
GRBL
| required | base_url |
| notes | Supports http://, tcp://, or bare host (TCP :23) |
LinuxCNC
| required | base_url |
| notes | Requires an external REST bridge (e.g. linuxcnc-rest) — not shipped by MEMAR |
Mach3
| required | base_url |
| notes | Requires an external REST bridge — not shipped by MEMAR |
Mach4
| required | base_url |
| notes | Targets a hypothetical REST bridge — unverified against real hardware |
OPC-UA
| required | base_url |
| notes | Format: opc.tcp://host:port/endpoint. Ships with placeholder NodeIds — reads nothing until you map them to your server. |
Haas
| required | base_url |
| notes | Targets a hypothetical REST bridge — unverified against real hardware |
FANUC
| required | base_url |
| notes | Targets a hypothetical FOCAS HTTP bridge — unverified against real hardware |
Siemens Sinumerik
| required | base_url |
| notes | Requires an external REST bridge — not shipped by MEMAR |
Fadal CNC
| required | base_url |
| notes | Targets a hypothetical Ethernet gateway / DNC bridge — unverified against real hardware |
OpenPnP
| required | base_url |
| notes | OpenPnP REST API at :8080 (configurable); no e-stop implementation yet |
Activating your license.
Three methods — all hot-apply without a redeploy except the env var method.
Env var (recommended for Docker / systemd)
MEMAR_LICENSE_KEY=eyJhbGciOiJFZERTQSIs... Restart the container or service after setting. The key is validated on startup and persists across restarts.
Admin UI (hot-apply)
Settings → License → paste key → Apply. Takes effect immediately — no restart required.
Auto-refresh heartbeat
Set MEMAR_LICENSE_HEARTBEAT_URL (defaults to the Stratum issuance server). MEMAR checks in hourly and auto-applies a fresh key each billing period. Air-gap installs must disable: set MEMAR_LICENSE_HEARTBEAT_URL="".
Device limits by tier
| Tier | Devices |
|---|---|
| Free | 5 |
| Maker | 15 |
| Pro | 40 |
| Farm | 150 |
| Scale | 500 |
| Workshop | 500 |
| Business | 2,000 |
| Enterprise | 10,000 |
| Air-Gap | 9,999 |
Free gets core monitoring and control. Every paid tier includes every MEMAR feature — the only differentiator between paid tiers is device capacity. View pricing →
Environment variable reference.
All configuration is via environment variables. Set them in .env (Docker) or /etc/memar.env (systemd).
Core 12
| Variable | Default | Description |
|---|---|---|
| MEMAR_JWT_SECRET | (required) | Session signing key. openssl rand -hex 32 |
| MEMAR_ADMIN_USER | admin | Initial admin username |
| MEMAR_ADMIN_PASSWORD | (random, logged) | Initial admin password — only logged on first run |
| MEMAR_PUBLIC_URL | http://localhost:9000 | Public base URL — used in billing redirects and SSO callbacks |
| MEMAR_CONFIG_PATH | config/devices.yaml | Path to device config file |
| MEMAR_DATA_DIR | data/ | Root directory for all persisted data |
| MEMAR_DB_PATH | data/memar.db | SQLite database file path; overrides MEMAR_DATA_DIR location |
| MEMAR_CORS_ORIGINS | (none) | Extra CORS origins, comma-separated — useful behind a reverse proxy |
| MEMAR_OWNER_EMAIL | (none) | Owner contact shown in license validation responses |
| MEMAR_ENV | production | Environment tag: production or development |
| MEMAR_DEMO_MODE | false | Read-only demo mode — injects sample data |
| MEMAR_NO_AUTH | false | Disable JWT auth entirely — dev/testing only, never in production |
Licensing 4
| Variable | Default | Description |
|---|---|---|
| MEMAR_LICENSE_KEY | (none) | Signed Ed25519 license key from Stratum |
| MEMAR_LICENSE_HEARTBEAT_URL | Stratum worker | Auto-refresh endpoint; set "" to disable. Air-gap installs must disable. |
| MEMAR_LICENSE_PUBLIC_KEY | (embedded) | Ed25519 public key — only override if running your own issuance server |
| MEMAR_LICENSE_PRIVATE_KEY | (none) | Ed25519 private key — only needed if you are the key issuance server |
Billing 4
| Variable | Default | Description |
|---|---|---|
| MEMAR_STRIPE_SECRET_KEY | (none) | Stripe API key (sk_live_…) |
| MEMAR_STRIPE_PRICE_MAKER / _PRO / _FARM / _SCALE | (placeholders) | Monthly Stripe price IDs |
| MEMAR_STRIPE_PRICE_*_ANNUAL | (placeholders) | Annual Stripe price IDs |
| MEMAR_STRIPE_WEBHOOK_SECRET | (none) | ⚠ Do not set if using the Cloudflare Worker — causes double key issuance |
Alerting & Integrations 5
| Variable | Default | Description |
|---|---|---|
| MEMAR_ALERT_EMAILS | (none) | Comma-separated email addresses for alert delivery |
| MEMAR_SMTP_HOST / _PORT / _USER / _PASSWORD / _FROM | (none) | SMTP credentials for email delivery |
| WEBHOOK_URL | (none) | Generic outbound webhook endpoint for fleet events |
| MEMAR_SLACK_WEBHOOK_URL | (none) | Slack incoming webhook URL for alerts |
| MEMAR_COOLANT_WEBHOOK_URL | (none) | Dedicated webhook for CNC coolant system alerts |
Analytics 1
| Variable | Default | Description |
|---|---|---|
| MEMAR_FILAMENT_COST_PER_KG | (none) | Filament cost in USD/kg — enables cost tracking in analytics |
SSO — OIDC 5
| Variable | Default | Description |
|---|---|---|
| MEMAR_OIDC_ISSUER | (none) | OIDC issuer URL (e.g. https://accounts.google.com) |
| MEMAR_OIDC_CLIENT_ID | (none) | OIDC client ID |
| MEMAR_OIDC_CLIENT_SECRET | (none) | OIDC client secret |
| MEMAR_OIDC_REDIRECT_URI | (none) | OAuth2 redirect URI — must match what is registered in the IdP |
| MEMAR_SSO_DEFAULT_ROLE | viewer | Role assigned to SSO users on first login |
SSO — LDAP 3
| Variable | Default | Description |
|---|---|---|
| MEMAR_LDAP_SERVER | (none) | LDAP server URL (e.g. ldap://host:389) |
| MEMAR_LDAP_USER_DN_TEMPLATE | (none) | DN template with {username} placeholder |
| MEMAR_LDAP_DEFAULT_ROLE | viewer | Default role for authenticated LDAP users |
Roles and access control.
MEMAR uses JWT (HS256) auth with three roles. Users are created via the admin UI. SSO roles map from OIDC claims or LDAP groups.
| Role | Capabilities |
|---|---|
| admin | Full access — device config, user management, license, audit logs, branding, SSO, webhooks |
| operator | Device control — start, pause, resume, cancel jobs; send gcode; adjust temps; jog axes |
| viewer | Read-only — live dashboard, metrics, job history, device status |
MEMAR_SSO_DEFAULT_ROLE on first login. LDAP users get MEMAR_LDAP_DEFAULT_ROLE. Override per-user in admin UI after first login.
Outbound event webhooks.
Configure a webhook URL and secret in admin UI → Webhooks. MEMAR signs every delivery with HMAC-SHA256. Queue: 500 events in-memory.
Events
| Event | Fires when |
|---|---|
| printer.error | Any device transitions to error state |
| printer.complete | Print/job completes successfully |
| printer.offline | Device stops responding to polling |
| printer.anomaly | AI anomaly detection flags unusual behavior |
| cnc.spindle_overspeed | Spindle RPM exceeds configured threshold |
| cnc.coolant_failure | Coolant system failure detected |
| cnc.tool_change | Tool change event |
| cnc.feed_rate_warning | Feed rate anomaly detected |
| cnc.production_complete | CNC production job completes |
Payload format
{
"type": "printer.complete",
"printer_id": "voron-1",
"data": {
"state": "complete",
"timestamp": "2026-06-26T14:22:01Z"
}
} Signature verification (Python)
import hmac, hashlib
expected = "sha256=" + hmac.new(
secret.encode(),
payload, # raw request body bytes
hashlib.sha256
).hexdigest()
# Constant-time compare against the X-MEMAR-Signature header
assert hmac.compare_digest(expected, request.headers["X-MEMAR-Signature"]) Overall Equipment Effectiveness.
MEMAR calculates OEE per device over the last 100 jobs. Fleet OEE is the sample-size-weighted average across all active devices.
How much of planned runtime the machine was actually running. Downtime, maintenance, and changeovers reduce this metric.
How close actual cycle time was to the estimated time. Requires M73 progress commands in gcode — null if not present.
The ratio of successful completions to total attempts. Failed or cancelled jobs reduce quality score.
OEE is null unless all three components are measurable. 85%+ is the industry benchmark for world-class manufacturing; 65%+ is typically considered good.
M73 gcode command reports estimated print time and progress percentage. Most modern slicers (PrusaSlicer, OrcaSlicer, SuperSlicer) can emit M73 — check your slicer's gcode settings. CNC jobs rarely have M73; Performance will be null for those devices.
Common issues.
Quick fixes for the most common problems. Check the GitHub issues if yours isn't here.
Adapter shows "offline" immediately
Wrong base_url, firewall block, or API key mismatch. Test the endpoint directly: curl -H "X-Api-Key: KEY" http://host:port/api/info
License key not activating
Verify the full base64 string — no line breaks or trailing spaces. Check: docker compose logs memar | grep license
OEE shows null
Performance requires M73 progress commands in gcode. Enable "Include M73 P/R commands" in your slicer, or accept null until enough M73-enabled jobs complete.
Bambu Labs not connecting
LAN mode must be enabled in the printer's network settings. Use the 8-digit access code from the printer display, not your account password.
GRBL adapter stuck in connecting
If using TCP mode, test: telnet host 23. Ensure a GRBL bridge is running (FluidNC, CNCjs, UGS) on the target host.
Heartbeat fails after purchasing
MEMAR_LICENSE_HEARTBEAT_URL must be reachable from the host. Air-gap installs: set MEMAR_LICENSE_HEARTBEAT_URL="" in .env to disable.
Admin password not in logs
Printed only on first run (when the database is initialized). If the DB already exists, set MEMAR_ADMIN_PASSWORD in .env to override.
Webhook not firing
Queue is 500 events in-memory. Check admin UI → Webhooks → Delivery Log. Verify your endpoint returns HTTP 2xx within 10 seconds.