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 97.4%
  • Just 0.8%
  • CSS 0.6%
  • Shell 0.6%
  • Dockerfile 0.4%
Find a file
David e0af4af364
All checks were successful
Check / clippy + fmt + tests (push) Successful in 54s
Build OCI container / Build and push mokosh-www image (push) Successful in 2m3s
Merge pull request 'refactor(ui): remove dead components and finish portal/admin stub pages' (#129) from feat/mapps-142-remove-dead-components into main
Reviewed-on: #129
2026-06-13 15:59:36 +02:00
.devcontainer feat: Split from Dioxus PSA 2026-05-03 20:58:31 -04:00
.forgejo/workflows chore(infra): clean up Dockerfiles, CI workflows, justfile, packaging 2026-06-13 02:48:10 -04:00
.idea refactor: rename mokosh-client to mokosh-apps 2026-05-29 14:50:58 +02:00
dev-docs docs: add 2026-06-06 platform audit to dev-docs/audits 2026-06-11 03:31:29 +02:00
oci-build chore(infra): clean up Dockerfiles, CI workflows, justfile, packaging 2026-06-13 02:48:10 -04:00
src Merge remote-tracking branch 'origin/main' into feat/mapps-142-remove-dead-components 2026-06-13 09:47:30 -04:00
.dockerignore feat: Dockerized dev server, login bypass, README 2026-05-03 23:28:41 -04:00
.env.example feat: Dockerized dev server, login bypass, README 2026-05-03 23:28:41 -04:00
.gitignore docs: add 2026-06-06 platform audit to dev-docs/audits 2026-06-11 03:31:29 +02:00
build.rs fix(ci): embed the commit hash in the OCI build so the footer is not "unknown" 2026-06-05 22:37:18 +02:00
bun.lock feat: Split from Dioxus PSA 2026-05-03 20:58:31 -04:00
Cargo.lock chore: sync Cargo.lock with the ammonia dependency 2026-06-06 00:47:17 +02:00
Cargo.toml chore(infra): clean up Dockerfiles, CI workflows, justfile, packaging 2026-06-13 02:48:10 -04:00
compose.dev-sso.yml feat(dev-sso): point the SPA OIDC issuer at bunyip-api 2026-06-05 13:34:01 -04:00
compose.yml refactor: rename mokosh-client to mokosh-apps 2026-05-29 14:50:58 +02:00
Dioxus.toml refactor: rename mokosh-client to mokosh-apps 2026-05-29 14:50:58 +02:00
Dockerfile chore(infra): clean up Dockerfiles, CI workflows, justfile, packaging 2026-06-13 02:48:10 -04:00
index.html feat: Split from Dioxus PSA 2026-05-03 20:58:31 -04:00
input.css feat(ui): themed global scrollbars instead of the default white trough 2026-06-05 22:54:56 +02:00
justfile chore(infra): clean up Dockerfiles, CI workflows, justfile, packaging 2026-06-13 02:48:10 -04:00
package.json chore(infra): clean up Dockerfiles, CI workflows, justfile, packaging 2026-06-13 02:48:10 -04:00
README.md chore(infra): clean up Dockerfiles, CI workflows, justfile, packaging 2026-06-13 02:48:10 -04:00

Mokosh Client

Cross-platform Dioxus client for the Mokosh Platform. Compiles to WebAssembly and runs in the browser.

Tech stack

  • Dioxus 0.7 (Rust UI framework, web + router features)
  • wasm32-unknown-unknown target
  • Tailwind CSS v4 via Bun (bun x @tailwindcss/cli)
  • just task runner
  • Docker Compose for the dev server
  • Caddy to serve the built bundle in production (see oci-build/)

Prerequisites

Install on the host:

  • Rust (stable)
  • wasm32-unknown-unknown target: rustup target add wasm32-unknown-unknown
  • Bun
  • just
  • Docker with the Compose plugin
  • Nushell 0.112.2 (used by the dev and create-release recipes)

The dev server itself runs inside Docker, so the host does not need dioxus-cli installed.

Quick start

just dev

This recipe:

  1. Detects the host LAN IP from the first non-loopback IPv4 interface (en*/eth*/br*/wlan*) via sys net.
  2. Exports it as HOST_IP.
  3. Runs docker compose up --build, which starts dev-mokosh-apps and binds port 4301 to ${HOST_IP}:4301.

Open the printed URL (e.g. http://172.16.100.120:4301). Hot reload is enabled.

Binding to the LAN IP rather than 0.0.0.0 keeps the dev server off the public internet when the host is a VPS.

Reaching the dev server from another container

Either:

  • Use the host LAN IP: http://${HOST_IP}:4301.
  • Or join the dev-mokosh-private-${USER} Docker network and use http://dev-mokosh-apps-${USER}:4301.

Justfile recipes

just                  # list recipes
just dev              # run the dev server in Docker (see above)
just css-build        # one-shot Tailwind build
just css-watch        # Tailwind watch mode
just check            # check-web, check-clippy, check-fmt
just check-web        # cargo check --target wasm32-unknown-unknown
just check-clippy     # cargo clippy --all-targets
just check-fmt        # cargo fmt --all --check
just fmt              # cargo fmt --all
just test             # cargo test
just build            # release WASM bundle (dx build --release)
just check-docker     # build the production OCI image as :check
just build-docker     # build the production OCI image as :local
just create-release   # cut a release branch and bump versions (see below)

Project layout

src/
  main.rs           # WASM entry point
  lib.rs
  components/       # button, card, form, icons, layout, modal, table
  hooks/
  modules/          # auth, contacts, tenants, tickets
  pages/            # admin, billing, calendar, contracts, dashboard, ...
  utils/

assets/             # built CSS and static assets (styles.css is generated)
input.css           # Tailwind entry; compiled into assets/styles.css
index.html          # HTML template for the WASM bundle

Cargo.toml          # Rust workspace + crate config
Dioxus.toml         # dx serve / dx build config (port 4300, name, bundle)
package.json        # Bun deps (Tailwind v4)
justfile            # task runner
compose.yml         # dev server stack
Dockerfile          # dev image (dx serve with hot reload)
oci-build/          # production image (Caddy serving the built bundle)

Ports

Port Where What
4300 Dioxus.toml [server] port used by dx for the runtime server config
4301 compose.yml dx serve dev port, published as ${HOST_IP}:4301:4301

Login bypass (dev only)

Copy .env.example to .env and set both ADMIN_EMAIL and ADMIN_PASSWORD. When both are set and non-empty at compile time, the WASM client starts pre-authenticated as that admin user and the /login route redirects straight to /dashboard.

The values are baked into the bundle by option_env! at build time, so changing them requires a rebuild. build.rs declares cargo:rerun-if-env-changed for both vars, so editing .env (followed by re-running just dev) invalidates the cache and rebuilds.

This is dev-only and the boundary is enforced by Docker:

  • compose.yml loads .env via env_file and forwards ADMIN_EMAIL / ADMIN_PASSWORD into the dev container, where dx serve bakes them into the WASM bundle.
  • The bypass branch is gated behind #[cfg(debug_assertions)] and is compiled out of release builds entirely. dx build --release (and the oci-build/Dockerfile image) emits a WASM bundle that has no bypass code at all.
  • .dockerignore excludes .env from every Docker build context, so the file never reaches the production builder even if it exists in the working tree.

Leave both vars unset to use the normal login screen.

Cargo features

  • web (default) - WASM/web build.
  • multi-tenant (default) - multi-tenant build.
  • single-tenant - single-tenant build (mutually exclusive with multi-tenant).

Production build

Two options:

Local WASM bundle:

just build

Output lands under target/dx/.

Production OCI image (Caddy + WASM bundle):

just build-docker        # tags as mokosh-apps:local
just check-docker        # tags as mokosh-apps:check (smoke build)

The image is built from oci-build/Dockerfile and serves the bundle with oci-build/Caddyfile.

Self-host deployment

Self-hosters can pull the pre-built multi-arch image from the public registry and run it with the reference compose file. No build, no registry login required.

cp oci-build/compose.example.yml compose.yml
# Edit the four MOKOSH_* env vars in compose.yml to point at your own
# mokosh-server API + OIDC issuer.
docker compose up --detach

The image is dev.a8n.run/psa-systems-public/mokosh-www. Tags:

  • :vX.Y.Z - pin to a specific release.
  • :latest - rolling, advanced on every push to main.

The image supports linux/amd64 and linux/arm64; compose pulls the variant matching the host kernel automatically.

Runtime config is supplied via env vars on the container, so a single image works across staging, production, and self-host:

Env var Purpose
MOKOSH_API_BASE API base URL the SPA calls (https://api.example.com/api/v1).
MOKOSH_OIDC_ISSUER OIDC issuer the SPA authenticates against.
MOKOSH_OIDC_CLIENT_ID Public-client ID registered with mokosh-server.
MOKOSH_HUB_BASE_URL Origin of the Bunyip hub for legacy login bookmarks.

The container's entrypoint writes a tiny /_mokosh_config.js from these on each start; the SPA reads it before falling through to its compile-time defaults. Restart the container to pick up changed values.

To apply an available update, bump the tag in compose.yml and run docker compose pull && docker compose up --detach. The SPA is stateless so there's no migration step.

Releases

create-release bumps the version in both Cargo.toml and package.json, commits to a release/vX.Y.Z branch, pushes, and prints the PR URL:

just create-release major     # X.0.0
just create-release minor     # 0.X.0
just create-release hotfix    # 0.0.X

The recipe refuses to run on a dirty tree, switches to main, rebases against origin/main, and aborts if the two version fields disagree. After the PR is merged, the create-release workflow tags and releases the version automatically.

Troubleshooting

Address already in use (os error 98) when starting just dev: Something else is bound to ${HOST_IP}:4301. Find it with ss --tcp --listening --numeric --processes 'sport = :4301' (use sudo to see the owning process) or docker ps --format 'table {{.Names}}\t{{.Ports}}' | grep 4301. Stop the conflicting process or change the port in compose.yml and Dockerfile.

HOST_IP is empty: The dev recipe reads sys net | where name =~ 'eth0|br0'. If neither interface has an IPv4 address, the recipe fails. Check ip --brief address show and edit the regex to match the right interface.