Mokosh (mythological/Slavic): The earth-mother goddess of weaving and women's work. Mascot: a slender figure at a loom under spring rain. Tagline: Weaves it all together.
  • Rust 93.9%
  • TypeScript 4.1%
  • Just 0.7%
  • JavaScript 0.4%
  • PLpgSQL 0.3%
  • Other 0.6%
Find a file
David 85fd769008
Some checks are pending
E2E / Playwright against staging (push) Waiting to run
Build OCI container / Build and push mokosh-api image (push) Has started running
Check / fmt + clippy + compile + tests (push) Has started running
Merge pull request 'fix(tickets): scope writes to tenant and validate FK references' (#209) from fix/pms-193-scope-tickets-writes-validate-fk into main
Reviewed-on: ⁨#209
2026-06-13 23:42:02 +02:00
.devcontainer feat: Split from Dioxus PSA 2026-05-03 20:58:07 -04:00
.forgejo/workflows Merge branch 'main' into feat/pms-198-converge-subsystems 2026-06-13 17:40:25 +02:00
.idea chore: Sync .gitignore with other repos 2026-05-09 22:43:12 -04:00
crates Merge origin/main into fix/pms-200-misc-correctness-concurrency-test-reliability 2026-06-13 17:13:36 -04:00
dev-docs refactor: converge duplicated subsystems and document architecture seams 2026-06-13 11:33:20 -04:00
e2e fix(e2e): correct OIDC redirect_uri default to /auth/callback 2026-06-13 17:22:35 +02:00
migrations fix(db): renumber portal_setup_tokens migration 041 to 042 2026-06-13 17:20:01 -04:00
oci-build fix(oci): drop image.source label causing dual-org package listing 2026-05-15 16:16:10 +02:00
scripts refactor: converge duplicated subsystems and document architecture seams 2026-06-13 11:33:20 -04:00
src Merge origin/main into fix/pms-193-scope-tickets-writes-validate-fk 2026-06-13 17:34:26 -04:00
tests Merge origin/main into fix/pms-200-misc-correctness-concurrency-test-reliability 2026-06-13 17:13:36 -04:00
.env.example Revert M1 image distribution work 2026-05-15 16:56:08 +02:00
.gitignore chore: Sync .gitignore 2026-06-06 13:21:44 -04:00
build.rs refactor: converge duplicated subsystems and document architecture seams 2026-06-13 11:33:20 -04:00
Cargo.lock refactor: converge duplicated subsystems and document architecture seams 2026-06-13 11:33:20 -04:00
Cargo.toml refactor: converge duplicated subsystems and document architecture seams 2026-06-13 11:33:20 -04:00
CLAUDE.md docs: add quickstart for fresh clones, plus CLAUDE.md 2026-05-28 12:58:37 -04:00
compose.dev-sso.yml feat(dev-sso): validate bunyip-issued tokens as a Resource Server 2026-06-05 13:34:01 -04:00
compose.dev.yml fix(ci): gate prod image push to main/tags; close CI coverage gaps 2026-06-13 10:27:03 -04:00
Dockerfile chore(build): bump rust-builder-glibc to v1.0.1 2026-06-06 22:27:59 -04:00
justfile feat(justfile): add dev-clean / dev-clean-all teardown recipes 2026-06-13 12:52:27 -04:00
README.md Revert M1 image distribution work 2026-05-15 16:56:08 +02:00

Mokosh Server

Professional Services Automation (PSA) platform for MSPs. REST API server built on Axum + SQLx + PostgreSQL. Secrets are sourced from a self-hosted Infisical instance.

Architecture

  • HTTP: Axum 0.8 (Tokio runtime) with Tower middleware (CORS, tracing, gzip, request limits, request-id).
  • Database: PostgreSQL 18 via SQLx (compile-time-checked migrations).
  • Secrets: Infisical (Universal Auth machine identity) reached over HTTP.
  • Email: Lettre (SMTP).
  • Auth: JWT (HS256) plus Argon2 password hashing.
  • Tenancy: feature-flagged. multi-tenant is the default; single-tenant is also supported.

Modules under src/modules/ cover the typical PSA surface area: tickets, contracts, contacts, billing, projects, assets, time_tracking, sla, rmm, knowledge_base, portal, reports, notifications, audit, auth, tenants, settings, calendar.

Binaries

Binary Purpose
mokosh-server Long-running HTTP API (Axum).
mokosh-bootstrap One-shot CLI that performs first-run setup of a fresh Infisical instance and writes the resulting Universal Auth credentials into .env.

Prerequisites

  • Rust toolchain (matches rust-toolchain.toml if present, otherwise stable).
  • Docker + Docker Compose v2.
  • just for the task runner.
  • Nushell 0.112.2 (used by several just recipes).
  • (No host-side PostgreSQL needed - the dev compose stack now bundles a postgres service for the app DB, published to the host on ${MOKOSH_HOST_BIND_IP}:${MOKOSH_PG_HOST_PORT:-5433} for sqlx-cli.)

Quick start

The dev stack lives in compose.dev.yml and is driven entirely through just. The first run copies .env.dev to .env (gitignored) and discovers the host's private LAN IP automatically.

# 1. Bring up the dev stack (mokosh-server + Infisical + Postgres + Valkey).
#    The recipe discovers the host bind IP from `sys net | where name =~ 'eth0|br0'`
#    and publishes mokosh-server on `${MOKOSH_HOST_BIND_IP}:${MOKOSH_PORT}`.
just dev

# 2. In a second terminal, bootstrap Infisical (one-time, after the
#    container is healthy). See "Infisical bootstrap" below for the
#    .env.infisical contents this needs.
just infisical-bootstrap

# 3. Re-run `just dev` so the server picks up the new INFISICAL_* values.
just dev-down
just dev

The API is then reachable at http://<MOKOSH_HOST_BIND_IP>:<MOKOSH_PORT> (defaults to the host's br0/eth0 IP on port 4301). The Infisical admin UI is reachable at http://localhost:28002.

Why a private LAN bind, not 127.0.0.1?

The dev host is a VPS on the public internet. Binding to 127.0.0.1 would hide the port from the internet but also prevent other Docker containers on the host from reaching it. Binding to a private LAN interface (br0/eth0, e.g. 172.16.x.x) keeps the port off the public internet and still allows sibling containers to connect.

Configuration

Variable Where set Purpose
COMPOSE_FILE .env Pins docker compose to compose.dev.yml.
DATABASE_URL .env Host-side connection string against the bundled postgres service (used by sqlx migrate, cargo run on the host).
MOKOSH_PG_DB, MOKOSH_PG_USER, MOKOSH_PG_PASSWORD .env App-database credentials. Dev defaults only.
MOKOSH_PG_HOST_PORT .env Host-published port for the postgres service. Default 5433.
MOKOSH_PORT .env TCP port for the API server (container and host bind).
MOKOSH_HOST_BIND_IP written to .env by just dev Host interface IP that the container's port is published on. Falls back to 127.0.0.1 when unset.
JWT_SECRET, ENCRYPTION_KEY .env API server secrets. Dev defaults only; rotate for any non-local environment.
ADMIN_EMAIL, ADMIN_PASSWORD .env Optional first-run admin bootstrap. Dev only; see "First-run admin bootstrap" below.
INFISICAL_* .env Infisical server config (bootstrap inputs) and Universal Auth client credentials (filled by mokosh-bootstrap).
RUST_LOG .env Tracing subscriber filter.

compose.dev.yml references every value via ${VAR} substitution and contains no hardcoded secrets. Required vars use ${VAR:?...} so compose fails loudly with a helpful message when a value is missing.

Recipes

just                       # list all recipes
just dev [args]            # bring up the dev stack (passes args to `docker compose up`, e.g. --detach)
just dev-down              # stop the dev stack (volumes preserved)
just dev-clean             # stop and remove volumes; remove .env (preserves .env.infisical)
just infisical-bootstrap   # one-time: drives Infisical first-run setup and rewrites .env
just check                 # cargo check + clippy + fmt --check
just check-clippy
just check-compile
just check-fmt
just fmt
just test
just build                 # cargo build --release --bins
just check-docker          # build the OCI image's builder stage only (validation)
just build-docker          # build the production OCI image (oci-build/Dockerfile)
just migrate-run           # apply pending migrations against $DATABASE_URL
just migrate-create <name> # create a new migration file
just create-release <bump> # bump version (major|minor|hotfix), push release branch, print PR link

First-run admin bootstrap

When both ADMIN_EMAIL and ADMIN_PASSWORD are set in .env AND the users table is empty, the server creates a super_admin user under the default tenant on startup. The account is marked active and email_verified_at = NOW(), so you can log in immediately without going through the signup or email-verification flow.

Once any user exists in the database, the env vars are ignored on subsequent startups. It is safe to leave ADMIN_EMAIL / ADMIN_PASSWORD in .env indefinitely.

The dev .env.dev ships with admin@example.com / devpassword12. Override locally:

# Override in .env (gitignored), then restart the stack.
"ADMIN_EMAIL=you@example.com\nADMIN_PASSWORD=at-least-12-characters\n" | save --append .env
just dev-down
just dev

DEV ONLY. Production deployments should provision the first admin through a real workflow (signup form, IaC, manual SQL) rather than environment variables.

Infisical bootstrap

The first-run setup is driven by mokosh-bootstrap (invoked via just infisical-bootstrap). The recipe loads .env.infisical (gitignored) before invoking the binary, so the admin password never lands in shell history.

# Create the gitignored credentials file.
"INFISICAL_ADMIN_EMAIL=admin@example.com
INFISICAL_ADMIN_PASSWORD=at-least-12-characters
" | save .env.infisical

# Run bootstrap. This signs up the admin, creates the project and machine
# identity, and writes INFISICAL_PROJECT_ID / INFISICAL_CLIENT_ID /
# INFISICAL_CLIENT_SECRET back into .env.
just infisical-bootstrap

# Verify .env now has the three Universal Auth values populated.
open .env | lines | where ($it | str starts-with 'INFISICAL_')

After bootstrap, restart the stack so the API server picks up the new credentials:

just dev-down
just dev

Database migrations

Migrations live in migrations/ and are embedded into the binary at compile time via sqlx::migrate!. They run automatically on server start when RUN_MIGRATIONS=true (the default).

# Apply pending migrations against $DATABASE_URL (host-side).
just migrate-run

# Create a new migration.
just migrate-create add_widgets_table

Compile-time migration validation requires the migrations/ directory to be present at build time. Both the dev Dockerfile and oci-build/Dockerfile copy it into the image.

Docker images

Two Dockerfiles, two purposes.

File Purpose
Dockerfile Dev image. Debug build, source mounted from the host via volumes, cargo run as the entrypoint. Used by compose.dev.yml.
oci-build/Dockerfile Production image. Multi-stage Alpine build (musl + lld), release binaries, non-root appuser, healthcheck on /api/v1/health. Built and published by the Forgejo workflow in .forgejo/workflows/build-oci-image.yml.

Repository layout

src/
  api/           Axum router composition.
  bin/           CLI binaries (mokosh-bootstrap).
  db/            Database connection + helpers.
  infisical/    Infisical client + first-run bootstrap.
  modules/       Feature modules (tickets, contracts, billing, ...).
  utils/         Shared helpers (errors, ...).
  lib.rs         Library crate entrypoint.
  main.rs        mokosh-server binary entrypoint.
migrations/      SQLx migrations (embedded at compile time).
oci-build/       Production OCI image (multi-stage Alpine).
Dockerfile       Dev OCI image (debug build, source-mounted).
compose.dev.yml  Dev stack: mokosh-server + Infisical + Postgres + Valkey.
.env.dev         Dev defaults (copied to .env on first `just dev`).
.env.example     Sanitized example for non-dev environments.
justfile         Task runner.
.forgejo/        CI workflows (Forgejo).
.devcontainer/   VS Code devcontainer config.

Releases

just create-release <major|minor|hotfix> bumps the version in Cargo.toml, creates and pushes a release/v<X.Y.Z> branch, and prints the PR URL. After the PR merges, the create-release workflow tags and publishes the release automatically.

License

Proprietary. See Cargo.toml.