Sign inSign up

mintjams/cms

By mintjams

•Updated about 19 hours ago

MintJams CMS: JCR 2.0 content repository, Webtop virtual desktop, built-in SAML 2.0 IdP/SP. Beta.

Image
Content management system
0

3.5K

mintjams/cms repository overview

⁠MintJams CMS

A lightweight Content Management System built on a simplified implementation of the Content Repository for Java Technology API 2.0 (JSR 283). It bundles a JCR-backed server runtime on Apache Felix together with a browser-based virtual desktop ("Webtop") for content authoring and administration. Zero-configuration SAML 2.0 (both Service Provider and Identity Provider) is included out of the box.

  • Server — Apache Felix runtime, JCR repository, SAML SP/IdP, Camel/Camunda integrations. Sources under bundles/⁠.
  • Client — "Webtop" virtual desktop and built-in apps (Content Browser, Identity Manager, BPM Console, BPMN/EIP Modeler, OSGi Console, etc.). Sources under webtop/⁠.

Status: 0.1.29-beta — public preview. APIs, on-disk formats, and bundled apps may change before 1.0. Installations of 0.1.23-beta or earlier cannot be upgraded in place — see Upgrading⁠.


⁠Quick start (Docker)

The published image bundles the full server runtime, all default bundles, and the pre-built Webtop assets. The only required configuration is the external URL the CMS will be reachable on.

docker run --rm \
  -p 8080:8080 \
  -e CMS_PUBLIC_BASE_URL=http://localhost:8080 \
  -v cms-repository:/data/repository \
  -v cms-secrets:/data/secrets \
  -v cms-index:/data/index \
  --tmpfs /opt/felix/tmp:size=512m,mode=0700 \
  mintjams/cms:0.1.29-beta

Then open http://localhost:8080/⁠ in a browser.

For a fixed deployment behind a reverse proxy, set CMS_PUBLIC_BASE_URL to the externally visible URL (e.g. https://cms.example.org) so the SAML SP and IdP generate correct redirect URLs.

⁠docker compose
services:
  cms:
    image: mintjams/cms:0.1.29-beta
    restart: unless-stopped
    environment:
      CMS_PUBLIC_BASE_URL: "http://localhost:8080"
    ports:
      - "8080:8080"
    volumes:
      - cms-repository:/data/repository
      - cms-secrets:/data/secrets
      - cms-index:/data/index
    tmpfs:
      - /opt/felix/tmp:size=512m,mode=0700

volumes:
  cms-repository:
  cms-secrets:
  cms-index:

⁠First login

On the first start, the container generates a random password for the built-in admin user and writes it (mode 0600) into the repository volume. Read it from the host:

docker exec <container> cat /data/repository/INITIAL_PASSWORD.txt

Log in as admin with that password, change it via Preferences, then delete the file:

docker exec <container> rm /data/repository/INITIAL_PASSWORD.txt

To set the initial password explicitly instead, pass -e CMS_INITIAL_ADMIN_PASSWORD=... on the first run.


⁠Configuration

⁠Environment variables
VariableRequiredPurpose
CMS_PUBLIC_BASE_URLyesExternal base URL (e.g. https://cms.example.org). Drives the SAML SP rootURL and IdP baseUrl. The container refuses to start without it.
CMS_INITIAL_ADMIN_PASSWORDnoInitial password for the auto-created admin user. If unset, a random password is generated and written to /data/repository/INITIAL_PASSWORD.txt.
CMS_SP_KEYSTORE_PASSWORDnoPassword for the auto-generated SP keystore. If unset, a random one is generated and written to /data/repository/SP_KEYSTORE_PASSWORD.txt. Stored AES-encrypted in etc/saml2.yml either way.
CMS_IDP_KEYSTORE_PASSWORDnoPassword for the auto-generated IdP keystore. If unset, a random one is generated and written to /data/repository/IDP_KEYSTORE_PASSWORD.txt. Stored AES-encrypted in etc/idp.yml either way.
MINTJAMS_CMS_SECRET_KEY_PATHnoPath to the AES master key. Defaults to /data/secrets/secret-key.yml.
CMS_CLUSTER_ENABLEDnotrue or false. Selects the clustered or standalone configuration files placed on first start, and when set overrides etc/repository.yml. See docker/README.md⁠.
CMS_DB_HOST, CMS_DB_PORT, CMS_DB_USER, CMS_DB_PASSWORDclusterThe shared database the clustered configuration files point at. CMS_DB_PASSWORD_FILE reads the password from a file (a Docker secret) instead. See docker/README.md⁠.
CMS_SEARCH_INDEX_PATHnoWhere this node keeps its search index. Defaults to /data/index. Node-local — never shared storage.
CMS_JAVA_OPTSnoExtra JVM flags (e.g. -Xmx4g), appended to the image's own. Set this rather than JAVA_TOOL_OPTIONS, which replaces what the entrypoint assembles.

A configuration file can take a value from the environment with ${env.NAME} (or ${env.NAME:-default}), from a file with NAME_FILE, or carry it encrypted as ENC[v1:...] — see docker/README.md⁠.

⁠Persistent volumes
MountWhy it must persist
/data/repositoryJCR content, generated SP/IdP keystores (*.p12), and the auto-generated etc/saml2.yml / etc/idp.yml. Losing this means starting from an empty repository.
/data/secretsThe AES master key that encrypts keystore passwords in the YAML files and any ENC[...] value. Losing this volume makes those encrypted values unrecoverable. Back it up separately.
/data/indexThis node's search index. Rebuilt automatically from the repository content when empty, so it need not be backed up — but it must stay node-local.
⁠Exposed port
PortDescription
8080HTTP. Terminate TLS at a reverse proxy and forward to this port.
⁠Zero-configuration SAML

On first boot the bundles create etc/saml2.yml, etc/idp.yml, and both keystores automatically. The SP trusts the co-located IdP via in-JVM OSGi services — no manual metadata exchange is required. CMS_PUBLIC_BASE_URL is the single source of truth for the external hostname; restarting with a new value retargets both SP and IdP. To federate with an external IdP, edit etc/saml2.yml after first boot; values written there take precedence over the auto-generated defaults.

⁠Sign-in methods and second factors

The built-in IdP offers two ways to sign in, chosen on the login page:

  • Passkey (WebAuthn): face or fingerprint recognition, a device PIN, or a security key. Passkeys are registered as discoverable credentials with user verification, so no user name is typed and a passkey alone counts as two factors.
  • Password, followed by a 6-digit code from an authenticator app when the user has turned on two-step verification (TOTP, RFC 6238). Ten single-use backup codes are issued with it for when the app is not at hand.

Users manage both in Preferences › Security (turn TOTP on or off, get new backup codes and download them, add, rename and remove passkeys). Every step that starts an enrollment or weakens a sign-in asks for the current password again. An administrator can turn off a user's TOTP and remove their passkeys for account recovery from the Identity Manager (user › Security), or through the GraphQL mutations disableTotp and deletePasskey; neither needs the user's password.

The SAML assertion tells the SP how the user authenticated (AuthnContextClassRef: PasswordProtectedTransport, TimeSyncToken, or urn:mintjams:idp:ac:classes:WebAuthn), and the SP records the factors in the session and the authentication token (saml2,password, saml2,password,totp, saml2,webauthn).

etc/idp.yml settings (all optional; the defaults are written on first boot):

KeyDefaultPurpose
webauthn.rpIdhost of baseUrlThe WebAuthn relying-party id. Passkeys are bound to it; changing it invalidates every registered passkey. Leave blank unless the IdP is served under a different host than the one users see.
webauthn.rpNameMintJamsThe name authenticators show when a passkey is created.
webauthn.origins[]Extra origins allowed to run WebAuthn ceremonies. The origin of baseUrl is always allowed.
totp.issuerMintJamsThe issuer shown in authenticator apps.
customLoginPageUrl(unset)Serve your own login page instead of the bundled one; see below.

Password hashes, TOTP secrets, backup codes and passkeys live in the system workspace under /home/credentials, which denies every privilege to everyone; only system, service and administrator sessions can read it. User profiles under /home/users carry no credentials.

⁠Branding the login page

The login page is a small single-page application: the sign-in flow (method choice, password, verification code, passkey) is served by the IdP at /idp/login/app.js and renders itself into an element with the id idp-login; the page around it is yours. Start from the bundled template, which the Webtop ships at /content/public/auth/login.html in the system workspace (reachable as /bin/cms.cgi/system/content/public/auth/login.html): change the logo, colours (the --idp-* custom properties theme the flow), background and footer, keep the idp-login element and the module script, and point the IdP at it:

customLoginPageUrl: /bin/cms.cgi/system/content/public/auth/login.html

Because the flow lives in app.js, a branded page keeps working when a later release adds a step. The flow reads <html lang> (English and Japanese are built in); window.IDP_LOGIN = { lang, labels, mount } set before the script loads overrides the language, individual labels, or the mount element.


⁠Upgrading

From 0.1.24-beta on, upgrading means replacing the image and keeping the volumes. On every start, the bundled content (Webtop apps, built-in provisioning, and the other default content shipped with the image) is brought in line with the running image in every workspace, including the removal of files the image no longer ships. Configuration files in the repository volume (etc/*.yml, <workspace>/etc/**/*.yml) are written only when missing and never overwritten; settings introduced by a later release use their defaults until you set them.

⁠Installations of 0.1.23-beta or earlier

These releases copied the bundled content into the repository volume only when the volume was first created, so replacing the image never updated it. They cannot be upgraded in place. Install 0.1.24-beta or later on new, empty volumes instead:

  1. In the old installation, export the content you want to keep as a CMS Archive from the Content Browser.
  2. Start the new version with new volumes for /data/repository and /data/secrets. A new admin password is generated (see First login⁠).
  3. Import the archives in the new installation's Content Browser, and reapply any changes you made to configuration files (for example an external IdP in etc/saml2.yml).

Until 1.0, a release may again require a fresh installation; such releases are announced in this section.

⁠Installations before the credential store

The release that introduced passkeys and two-step verification moved password hashes out of the user profiles into /home/credentials (see Sign-in methods and second factors⁠). Passwords stored on profiles by earlier releases are not read any more, so nobody, admin included, can sign in to an upgraded repository. Install this release on new, empty volumes following the steps above.


⁠Maintenance

⁠Rebuilding the search index

Administrators can rebuild the full-text search index from the Webtop Tasks app (Start a process → Search Index Rebuild) — for example after changing the search configuration under <workspace>/etc/search. The rebuild runs in the background on every cluster node while search and content updates keep working; a progress task shows every node live and supports abort and re-running failed nodes. See documents/search-index-rebuild.md⁠.

⁠Webtop Mail

The Webtop Mail app downloads IMAP mail in the background, the first time only for a period set per account, and never deletes mail on the server. Where the mail is kept, how the synchronization runs and how to run a pass by hand is described in documents/webtop-mail.md⁠.


⁠Platform support

The published image is a multi-arch manifest covering linux/amd64 and linux/arm64. Docker automatically pulls the variant matching the host, so Apple Silicon and arm64 servers run natively — no emulation required. Native JavaScript support (V8 via JNI) is delivered by per-architecture fragment bundles (org.mintjams.rt.cms.linux.x86_64 and org.mintjams.rt.cms.linux.arm64); the runtime loads the libraries for the running architecture and ignores the rest. See docker/README.md⁠ for details.


⁠Repository layout

bundles/    Server-side OSGi bundles (JCR, CMS, SAML SP/IdP, Camel, Camunda, ...)
webtop/    Client-side virtual desktop and built-in apps (TypeScript + Rollup)
docker/    Dockerfile, entrypoint, compose example, image seed (bundled assets, default configuration)
scripts/   docker-build.sh / docker-build.ps1 wrappers around `docker buildx`,
           assemble-seed.sh / assemble-seed.ps1 to lay the Webtop into the seed

⁠Building from source

The published image is produced from this repository. If you want to build it yourself (custom bundles, private fork, etc.), see docker/README.md⁠ for the full build pipeline:

  1. Produce a felix-dist/ directory at the repo root (Felix runtime + the bundles compiled from bundles/).
  2. Build the Webtop (cd webtop && npm run build:prod) and run ./scripts/assemble-seed.sh (or the PowerShell equivalent) to lay it into docker/seed/.
  3. Run ./scripts/docker-build.sh -v <version> (or the PowerShell equivalent) to produce a mintjams/cms:<version> image.

Per-component build instructions live in webtop/README.md⁠ for the client and in each bundle's own metadata for the server.


⁠License

MIT. See LICENSE⁠ for the project and docker/THIRD_PARTY_LICENSES.md⁠ for the inventory of third-party software shipped inside the container image.

⁠Trademarks

All trademarks are the property of their respective owners.


Built with ❤️ by MintJams Inc.⁠

Tag summary

Content type

Image

Digest

sha256:84ea24943…

Size

389.8 MB

Last updated

about 19 hours ago

docker pull mintjams/cms