OPERATIONS · API · CLIENTS

Moonli Operations Guide

A standalone reference for operating, integrating, deploying, backing up, and updating the Moonli generation service.

Return to Moonli

Contents

01 Architecture and access

Moonli runs three tagged generation profiles behind Nginx. Pipelines 1 and 2 complete through one synchronous POST /v1/generate. Pipeline 3 mirrors the production TouchDesigner interaction with two operations: audio normalization followed by three-image generation. The pipeline tag is part of the request; Android and TouchDesigner are not separate API pipelines.

Operator authentication uses an Access Key of at least 24 characters. A successful login creates a short-lived HttpOnly SameSite session; state-changing operator requests also require CSRF. Rotating the Access Key revokes all browser sessions. Client requests use independent Bearer credentials plus persistent device identities. No VPN or mTLS is required.

02 First production startup

  1. Configure the public hostname, allowed hosts, trusted TLS files, and independent random service credentials.
  2. Choose Google for the required providers and enter exact model names in Configuration. Production rejects mock providers, blank model names, development credentials, wildcard hosts, and a Google key placed in .env.
  3. Start the stack, sign in, and configure the required pipeline-specific Google API keys and models on Production. Starting without these keys is allowed so they can be provisioned through the browser; Google-backed calls for a pipeline remain unavailable until its key exists.
  4. Run a text and audio test for each required pipeline, download the artifacts, and verify a logical backup before connecting real clients.

Each persistent pipeline Google key is verified through Google's models API and written atomically to a pipeline-specific file in the dedicated secrets volume. The backend never returns the value or a mask. Keys are excluded from .env, browser storage, audit, backups, and release artifacts.

If direct Google access is region-blocked, open Configuration → Routing, paste a VLESS Reality/TCP/Vision connection, enable the switch, and save. The connection is stored only in the secrets volume and is never returned. The internal Xray sidecar reloads it automatically and has no public host port. Transcription, normalization, Pipeline 3 prompt translation, and image generation all follow the selected route. Disabling the switch restores direct Google routing.

03 Production client API

Text request

POST /v1/generate HTTP/1.1
Host: moonli.example.com
Authorization: Bearer <client-api-key>
X-Moonli-Device-Id: td-02941846
Idempotency-Key: 4c4058a6-fd85-4cf3-bf18-7fa475dd0ceb
Content-Type: application/json

{"type":"text","pipeline":"pipeline-1","text":"A red tree on a small island"}

Audio request

POST /v1/generate HTTP/1.1
Host: moonli.example.com
Authorization: Bearer <client-api-key>
X-Moonli-Device-Id: aa-26093758
Idempotency-Key: f78a3993-04c5-40d0-bc46-60dbc154106e
Content-Type: multipart/form-data; boundary=...

type=audio
pipeline=pipeline-2
audio=<audio file>

Each request selects exactly one server-owned profile: pipeline-1, pipeline-2, or pipeline-3. Do not send provider, palette, stage, or table fields. The server rejects missing, unknown, or misspelled tags. Audio type and size are validated before provider work begins. Pipeline 3 sends both operations to /v1/generate: audio as multipart, then the returned normalized text as JSON using a fresh operation UUID.

Idempotency. Use a new UUID for a new logical request. If transport fails, retry the identical payload with the same key. A completed result is replayed without another generation. Reusing the key with a changed payload produces IDEMPOTENCY_CONFLICT; a concurrent duplicate produces GENERATION_IN_PROGRESS and Retry-After.

04 Device identity

On first start, TouchDesigner creates td- plus eight cryptographically random decimal digits; Android creates aa- plus eight such digits. The client persists that value in installation-local storage and sends it on every generation request as X-Moonli-Device-Id.

The server automatically registers the identifier and counts every production attempt. The Devices page displays type, registration time, lifetime request count, and status. Blocking stops the device before the pipeline starts; unblocking restores its access. The prefix is descriptive only: either device type may call any pipeline. Pipeline-3 TouchDesigner scripts share the same persisted device ID across normalization and generation. The device ID is not a credential and never replaces Bearer authentication.

05 Processing pipeline

StageInputOutput and rule
1 · TranscriptionAudioSource text; omitted for text input.
2 · NormalizationSource textConcise English visual intent with filler and test commentary removed.
3 · Prompt builderNormalized phrasePipeline-specific technical prompt with canvas, palette, and style constraints.
4 · GenerationTechnical promptProvider result converted to PNG.
5 · QuantizationProvider PNGExact versioned palette using nearest CIE Lab color and optional 3×3 cleanup.
6 · ValidationQuantized PNGStrict zero-tolerance color and alpha report.
7 · VectorizationStrict PNGPalette-safe SVG where applicable.
8 · SegmentationVector/raster dataStable ordered layer package for pipeline-2.

Normalization is intentionally independent from Prompt Builder. A spoken request can become a phrase such as cute penguin icon; the builder then adds the technical instructions. This prevents greetings, hesitations, and test narration from becoming part of the image subject.

Pipeline 3 normalizes every request into concise Russian. During the second client operation, its Prompt Building stage translates that Russian phrase into concise English without adding details, then sends the English prompt to three independent Google image calls under the configured system instruction. English input at the translation boundary passes through unchanged. It skips quantization, validation, vectorization, and segmentation.

06 Artifact contracts

pipeline-1 returns image/png as moonli.png. pipeline-2 returns application/vnd.moonli.layers+zip as moonli-layers.zip. Those processed responses include run, digest, device, and replay headers. Pipeline-3 normalization returns only normalized text; pipeline-3 generation returns a ZIP containing only three 1024×1024 RGB JPEGs named image_1.jpg, image_2.jpg, and image_3.jpg.

manifest.json
composite.png
layers/00.png
...
layers/11.png

The layered contract always contains twelve full-canvas RGBA slots. Unused slots are transparent PNGs with used:false. Every visible pixel is fully opaque and equals its declared palette color; transparent pixels are exactly zero RGBA. The server validates safe relative names, duplicate members, checksums, dimensions, slot ordering, exact colors, alpha, ZIP integrity, and pixel-perfect layer recomposition.

The Test Calls full-run ZIP is diagnostic and contains every applicable intermediate plus pipeline and input-type metadata. For pipeline 3 this means source text or audio, optional transcription, normalized Russian text, the translated English prompt, three 1024×1024 JPEG variants, the nested image ZIP, and an execution trace; only the post-processing stages are marked not_applicable.

07 Operator console

The test-stage key exists only in the current browser tab and is cleared on refresh or sign-out. It is separate from the three persistent production keys.

08 Backup and recovery

The logical ZIP uses format moonli-logical-backup, schema version 2, and a SHA-256 digest for every member. It includes runs, usage, registered devices and block state, non-secret settings, operator verifier, retained audit, and referenced completed artifacts. It excludes all Google/client/updater keys, cookies, sessions, .env, staging files, and releases.

Restore validates a 128 MiB compressed limit, a 256 MiB uncompressed limit, compression ratio, member count, path safety, link rejection, duplicate names, schema, and every digest before mutating state. It first creates a pre-restore snapshot and automatically reapplies it on failure. The five newest restore points are kept. A successful restore revokes browser sessions.

Audit retention is bounded by 10,000 events, 30 days, and 64 MiB estimated payload. Usage retention is bounded by 1,000,000 rows and 365 days. Container stdout rotates at three files of 10 MiB. Protect the secrets volume, including Google keys and optional VLESS routing state, with a separate encrypted host-level backup because it is intentionally absent from logical snapshots.

09 Domain deployment

  1. Point the Moonli hostname to the server and allow inbound TCP 80/443.
  2. Install the trusted certificate and private key in the configured Nginx TLS paths.
  3. Use independent strong operator, client, catalog, restore, and updater credentials.
  4. Configure exact providers and Google models, then validate the production Compose configuration.
  5. Start the stack, verify public /health and loopback /readyz, then save the required pipeline Google keys in the UI.
docker compose --env-file /opt/moonli/.env \
  -f docker-compose.yml -f compose.production.yml config
docker compose --env-file /opt/moonli/.env \
  -f docker-compose.yml -f compose.production.yml up -d --wait
curl --fail https://moonli.example.com/health

Never expose 8000 or 18000 externally and never copy a workstation .env to the server. Unknown hosts return 421. Generation may take several minutes, so Nginx and clients use timeouts of at least 300 seconds. Keep normal TLS certificate verification enabled.

10 Updates and rollback

The host updater is based on psewdon1m-exocortex/updater at the pinned reviewed commit and communicates with Moonli through a Unix socket. The application never receives the Docker socket. It can request only a semantic Moonli version and cannot inject commands, image names, repositories, URLs, or service names.

Stable releases use moonli-vX.Y.Z, a signed-off moonli-release.json, a checksummed Compose bundle, and an immutable container image digest. Before mutation, Moonli creates a backup. The updater validates the release, changes image/version atomically, and checks loopback and public health. Failure restores the previous version and backup.

Important: one-click updates are image-only. Compose, Nginx, mount, or host-unit changes require the reviewed repair/install path after downloading an operator snapshot.

11 Errors and troubleshooting

{"error":{"code":"PALETTE_VALIDATION_FAILED","message":"Unable to generate an image that matches the allowed palette."}}
CodeMeaning and action
INVALID_DEVICE_IDUse td-######## or aa-######## and persist it.
DEVICE_BLOCKEDThe operator blocked this installation; review it on Devices.
NO_VISUAL_SUBJECTSpeech was recognized, but it did not say what should be drawn. Ask for a concrete visual subject; do not retry automatically.
UNAUTHORIZEDThe Bearer credential is missing or wrong.
GOOGLE_KEY_NOT_CONFIGUREDSave the persistent key on Production.
PROMPT_NORMALIZATION_FAILEDCheck normalization model access, endpoint, key, and timeout.
PROMPT_TRANSLATION_FAILEDCheck the Pipeline 3 translation instruction, model access, endpoint, key, and timeout.
GENERATION_IN_PROGRESSHonor Retry-After and retry the same payload/key.
IDEMPOTENCY_CONFLICTA changed request reused a key; allocate a new UUID.
RATE_LIMITEDHonor HTTP 429 and Retry-After.
PALETTE_QUANTIZATION_FAILED / PALETTE_VALIDATION_FAILEDInspect generated and quantized PNG artifacts from a full run.

Other stable codes include INVALID_INPUT, AUDIO_TOO_LARGE, TRANSCRIPTION_FAILED, PROMPT_BUILD_FAILED, IMAGE_GENERATION_FAILED, OUTPUT_VALIDATION_FAILED, RESULT_EXPIRED, and INTERNAL_ERROR. Use X-Moonli-Run-Id to correlate a response with the redacted server audit.

12 Android and TouchDesigner integration

TouchDesigner

Android

Android and TouchDesigner source projects are intentionally not edited by the backend repository. The ready production generation replacement is stored at integrations/touchdesigner/pipeline3_generation.py; apply it in the TouchDesigner project and provision an independent credential per installation where practical.