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 94.6%
  • TypeScript 3.7%
  • Nushell 0.5%
  • Just 0.5%
  • JavaScript 0.3%
  • Other 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
longjacksonle 989bd49d70
Some checks failed
E2E / Playwright against staging (push) Failing after 2m6s
Check / fmt + clippy + build + tests (push) Successful in 2m50s
Build OCI container / Build and push mokosh-server image (push) Successful in 5m5s
Integration / integration tests (push) Successful in 6m29s
Merge pull request 'Server-side drafts for the request-form builder (PMS-759)' (#514) from feat/PMS-759-server-side-form-drafts into main
Reviewed-on: #514
2026-08-11 17:10:59 +02:00
.cargo build: depend on shared dunite-stripe-core crate (DEV-514) 2026-08-03 12:04:44 -04:00
.devcontainer feat: Split from Dioxus PSA 2026-05-03 20:58:07 -04:00
.forgejo/workflows fix(ci): check disk headroom before restoring the cargo cache 2026-08-06 16:09:27 -04:00
.idea chore: Sync .gitignore with other repos 2026-05-09 22:43:12 -04:00
crates fix(tenants): merge branding instead of replacing it wholesale 2026-08-10 14:15:25 -04:00
docs fix(auth): repair the JIT placeholder email once bunyip verifies it 2026-08-06 05:13:13 -04:00
e2e fix(e2e): make the TOTP 2fa step window-safe to stop /login/2fa stalls 2026-08-02 16:53:16 -04:00
migrations feat(forms): add a table for server-side builder drafts 2026-08-11 11:05:54 -04:00
oci-build fix(oci): create the uploads directory in the runtime image 2026-08-10 14:02:24 -04:00
scripts Merge branch 'main' into chore/PMS-720-oci-gha-cache-guard 2026-08-04 19:18:37 +02:00
src feat(forms): read, write and discard a builder draft 2026-08-11 11:05:54 -04:00
tests test(forms): cover draft upsert, ownership and retirement 2026-08-11 11:05:54 -04:00
.env.example feat(forms): use the organisation contact and logo on the client's surfaces 2026-08-10 12:37:35 -04:00
.gitignore docs: sweep remaining .env.dev references to .env.example (PMS-708) 2026-08-03 09:57:41 -04:00
build.rs refactor: converge duplicated subsystems and document architecture seams 2026-06-13 11:33:20 -04:00
Cargo.lock Release v0.11.0 2026-08-07 18:48:53 +02:00
Cargo.toml Release v0.11.0 2026-08-07 18:48:53 +02:00
CLAUDE.md Merge branch 'main' into chore/PMS-720-oci-gha-cache-guard 2026-08-04 19:18:37 +02:00
compose.dev.yml docs: correct .env provenance in CLAUDE.md + compose guards (PMS-708) 2026-08-03 09:57:41 -04:00
Dockerfile chore(build): bump rust-builder-glibc to v1.0.1 2026-06-06 22:27:59 -04:00
justfile fix(just): run the integration suites as the superuser role 2026-08-06 13:46:55 -04:00
README.md docs: sweep remaining .env.dev references to .env.example (PMS-708) 2026-08-03 09:57:41 -04:00
rust-toolchain.toml chore(toolchain): pin rust-toolchain.toml to 1.94.1 (PMS-412) 2026-06-26 06:12:20 -04: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: multi-tenant only. Every service method takes an explicit tenant_id; the former single-tenant cargo feature was removed in PMS-262.

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 generates .env (gitignored) from the committed .env.example, minting fresh random values for every self-owned secret, 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 test-integration      # Postgres-backed tests/*.rs suite (mirrors CI integration.yml)
just install-hooks         # install git pre-commit hook -> runs `just pre-commit`
just pre-commit            # fast, DB-free checks (mirrors CI check.yml)
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 generated .env ships ADMIN_EMAIL / ADMIN_PASSWORD empty (they are blank in .env.example), so no admin is bootstrapped until you set them. To create a dev admin, set both and restart:

# Set 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.example     Committed template; `just dev` generates `.env` from it with fresh per-clone secrets.
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.