Add an admin "Access" page to gate who can sign into the admin GUI via Google OAuth. Replaces the single env-var domain check with GUI-managed policy: - Signup mode (per-login decision): open (domain match auto-approves), approval (domain sign-ins queue as pending for admin approval), or allowlist/closed (only pre-approved emails). - Multiple allowed domains (seeded once from MT_OAUTH_ALLOWED_DOMAIN). - User allow-list: approve pending, add, block (kept so open-mode can't silently re-provision), remove. All actions audited. - adminAuth re-validates the OAuth session against the store each request, so a block/removal revokes access immediately rather than at 12h cookie expiry. HTTP basic auth still bypasses (no lockout risk while tightening policy). New store tables: settings (signup_mode, allowed_domains) + users. Blocked/ queued logins re-render the sign-in page with a status banner. Tests cover domain matching (incl. suffix-spoof) and all three signup modes. Authored-by: Moko Consulting Claude-Session: https://claude.ai/code/session_015UauDPfYC8S2mdUhoSK8zQ
MokoTranslate
Go-based admin GUI + API gateway for a self-hosted LibreTranslate translation server.
LibreTranslate ships a great translation engine and REST API, but its only web
surface is a translation playground — there is no admin GUI. API keys are
managed with the ltmanage keys CLI against a SQLite database, rate limits are
env vars, and metrics are a raw Prometheus endpoint. MokoTranslate fills that
gap: it sits in front of a private LibreTranslate instance as the single public
door and gives you a browser dashboard for keys, usage, and health.
client ──► MokoTranslate ──► LibreTranslate
:8080 :5000 (private, keys OFF)
• API-key auth
• per-key rate limits
• per-key usage logging
• admin dashboard
• /healthz
Why a gateway (not a sidecar)
LibreTranslate does not record per-key usage, so an admin panel that merely reads its SQLite key DB could never show usage analytics. By making MokoTranslate the front door — it owns authentication and logs every request — the dashboard has real data, and the engine behind it becomes swappable (LibreTranslate today, something else later) without changing how clients authenticate.
Features
- API-key management — issue, label, rate-limit, and disable keys from the web UI. Keys are shown once at creation and stored only as SHA-256 hashes.
- Per-key usage — request counts, characters proxied, last-used, plus a recent-activity feed.
- Per-key rate limiting — requests/minute, configurable per key (0 = unlimited).
- Reverse proxy to the translation endpoints (
/translate,/translate_file,/detect,/languages,/frontend/settings). - Translation playground (
/admin/playground) — test translations from the browser; talks to the engine directly, no key needed. - Prometheus
/metrics(admin-auth protected) — total + per-key request/char counters and engine reachability, for the monitoring stack. - Health endpoint (
/healthz) for container/orchestrator probes.
Client usage
Clients authenticate to MokoTranslate with a bearer token (the issued mtk_… key):
curl -X POST https://translate.mokoconsulting.tech/translate \
-H "Authorization: Bearer mtk_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"q":"Hello, world","source":"en","target":"es"}'
?api_key=mtk_… is also accepted for compatibility with LibreTranslate clients.
/languages and /frontend/settings are open (no key) for capability discovery.
Configuration
All settings are environment variables (MT_*):
| Variable | Default | Purpose |
|---|---|---|
MT_LISTEN_ADDR |
:8080 |
Address to bind |
MT_LIBRETRANSLATE_URL |
http://127.0.0.1:5000 |
Private upstream engine |
MT_DATA_DIR |
/data |
Writable data directory |
MT_STORE_PATH |
<MT_DATA_DIR>/mokotranslate.json |
JSON store location |
MT_ADMIN_USER |
admin |
Admin basic-auth username |
MT_ADMIN_PASSWORD |
(unset) | Admin basic-auth password — required to use the admin UI (fail-closed if empty) |
MT_PUBLIC_BASE_URL |
https://translate.mokoconsulting.tech |
Public URL shown in the UI |
MT_DEFAULT_RATE_PER_MIN |
60 |
Default per-key rate when a key sets none |
MT_REQUIRE_KEY |
true |
Require an API key on translation endpoints |
Run locally
cp .env.example .env # then set MT_ADMIN_PASSWORD
docker compose up -d # brings up LibreTranslate (private) + MokoTranslate
# admin UI: http://localhost:8080/admin/ (basic auth: admin / <MT_ADMIN_PASSWORD>)
First boot downloads all LibreTranslate language models (~15–20 GB), so the engine's healthcheck has a long start period.
Architecture / layout
source/
main.go entry point, graceful shutdown
config/ MT_* environment configuration
store/ pure-stdlib JSON store (keys, usage, events)
gateway/ reverse proxy to LibreTranslate
httpapi/ HTTP server, admin handlers, gateway handler, middleware
templates/ embedded server-rendered HTML
Storage
MokoTranslate v1 uses a pure standard-library JSON store (atomic
temp-file + rename), so the module has zero third-party dependencies — no
go.sum, a fully reproducible CGO_ENABLED=0 static binary, and a tiny image.
It keeps per-key aggregate usage plus a bounded ring of recent events. If you
later need historical time-series (usage-over-time charts, retention), the
documented upgrade path is to swap store for SQLite behind the same interface.
Rate limiting
The per-key limiter lives in source/httpapi/middleware.go. Its Allow() method
is intentionally a stub (allow-all) so you can choose the strategy — sliding
window log, fixed window, or token bucket — that best fits your traffic. See the
TODO(you) in that file.
Deployment
Production runs on the beelink as a .vault Docker stack at
translate.mokoconsulting.tech, deployed through the restricted per-repo deploy
pattern (deploy-mokotranslate → mokocli → server-side deployer). The
.mokogit/workflows/deploy-{dev,rc,prod}.yml thin triggers are synced from
Template-Go.
License
GPL-3.0-or-later. See LICENSE. Copyright © 2026 Moko Consulting.