Files
neural-hunt/HOSTED_SERVICE.md
groot 47c523dd98
All checks were successful
release-tag / release-image (push) Successful in 3m51s
RC-14
2026-08-14 06:17:30 +02:00

13 KiB

Neural Hunt Hosted Service (optional)

V4.1 splits the deployment into three dedicated Docker images. V4.0 added an optional commercial control plane without changing the normal game server. The default docker compose up still starts only app. For a local source build, build all three images first and then start the hosted layer:

docker compose --profile images build app customer-service worker-image
docker compose --profile hosted up -d

With Docker Buildx the three images can also be built/tagged in one operation:

docker build -f Dockerfile.server -t neuralhunt-server:local .
docker build -f Dockerfile.customer-service -t neuralhunt-customer-service:local .
docker build -f Dockerfile.worker -t neuralhunt-worker:local .

Three Docker images

V4.1 no longer ships one monolithic runtime image. Dockerfile has three final targets:

  • server -> neuralhunt-server:local (game/API + private game admin)
  • customer-service -> neuralhunt-customer-service:local (portal/billing/control plane)
  • worker -> neuralhunt-worker:local (CLI hunting agent only)

The tags are configurable through .env / the build environment:

NEURALHUNT_SERVER_IMAGE=registry.example.com/neuralhunt/server:v4.1
NEURALHUNT_CUSTOMER_IMAGE=registry.example.com/neuralhunt/customer-service:v4.1
CS_WORKER_IMAGE=registry.example.com/neuralhunt/worker:v4.1

CS_WORKER_IMAGE is both the worker build tag and the exact image reference the Customer Service passes to Docker Engine. With CS_WORKER_AUTO_PULL=true (the default), a missing worker image is pulled lazily when a customer first needs a worker. For a private registry, configure a read-only deploy/robot credential:

CS_WORKER_REGISTRY_SERVER=registry.example.com
CS_WORKER_REGISTRY_USERNAME=neuralhunt-worker-pull
CS_WORKER_REGISTRY_PASSWORD=<read-only-token>

Those credentials are converted to Docker's X-Registry-Auth header only for the image-pull request and are never passed to managed worker containers. Set CS_WORKER_AUTO_PULL=false if production policy requires images to be pre-pulled instead.

The dedicated worker image already declares /app/neuralhunt-client as its ENTRYPOINT. CS_WORKER_ENTRYPOINT should therefore stay empty. It exists only as a compatibility override for older monolithic images.

For registry publishing with Buildx:

export NEURALHUNT_SERVER_IMAGE=registry.example.com/neuralhunt/server:v4.1
export NEURALHUNT_CUSTOMER_IMAGE=registry.example.com/neuralhunt/customer-service:v4.1
export CS_WORKER_IMAGE=registry.example.com/neuralhunt/worker:v4.1
# CI/CD: Dockerfile.server, Dockerfile.customer-service und Dockerfile.worker getrennt bauen/pushen.

Ports and trust zones

  • app:8080 — public Neural Hunt game
  • app:8081 — private game admin + private Customer-Service delegation API
  • customer-service:8090 — customer portal (put behind HTTPS)
  • customer-service:8091 — private/VPN Customer-Service admin
  • customer-service:8092 — Docker-network-only worker registration

Do not route 8081, 8091 or 8092 to the public Internet.

Local go run test

When both processes run directly on the same development machine, use:

CS_GAME_PUBLIC_URL=http://127.0.0.1:8080
CS_GAME_ADMIN_URL=http://127.0.0.1:8081

The Docker hostname app only exists inside the Compose network. docker-compose.yml overrides the two values to http://app:8080 and http://app:8081 automatically. The Customer Service now probes the authenticated game control plane at startup and prints a concrete warning if the URL points to the public listener, DNS is wrong, or CUSTOMER_SERVICE_SHARED_SECRET differs.

PrePaid billing model

The reference implementation bills running worker time, not individual submitted guesses. This makes the commercial unit explicit and predictable, but it is only a billing design; it does not by itself determine the legal/payment- provider classification of a paid prize/chance product. CS_WORKER_CREDITS_PER_MINUTE=1.0 means one paid minute of one worker consumes one credit. The next minute is debited atomically before it is allowed to continue; when there is insufficient balance the service stops the worker. A 2-second reconciliation loop also catches containers that stop or exit outside the portal. If Docker fails before a newly purchased start minute actually begins, that start debit is automatically refunded to the ledger.

Credit packages are configured server-side:

CS_CREDIT_PACKAGES=starter:499:EUR:60;plus:1999:EUR:300;power:4999:EUR:900

The four fields are id:price_cents:CURRENCY:credits. These are examples, not a pricing recommendation.

PayPal

PayPal is disabled by default and should first be configured in sandbox:

PAYPAL_ENABLED=true
PAYPAL_ENVIRONMENT=sandbox
PAYPAL_CLIENT_ID=...
PAYPAL_CLIENT_SECRET=...
PAYPAL_WEBHOOK_ID=...

The browser never receives the PayPal secret. Customer Service creates Orders server-side, redirects the user to the returned approval URL, captures the Order server-side after return and reconciles the captured amount/currency against the package stored before crediting the ledger. Webhooks are verified through PayPal's webhook-signature verification API. Ledger references make a completed Order idempotent.

Live PayPal is additionally locked until:

PAYPAL_LIVE_APPROVAL_ACK=I_HAVE_PAYPAL_APPROVAL

is set. This is intentional: products involving payment plus prizes/chance can require provider approval and legal review. Do not use that flag as a substitute for the approval/review itself.

Manual test/support credits

Manual top-ups are a separate protected bypass and are off by default:

CS_ALLOW_MANUAL_CREDITS=1

They are available only from the private Customer-Service admin listener on 8091 after its own HttpOnly admin session. Each grant is written to the same immutable-style credit ledger with a unique manual:* reference. Keep the flag off on a normal public production deployment unless support operations require it.

Multiple workers and reward ownership

Each managed worker gets:

  • its own P-256 Neural Hunt identity in a dedicated Docker named volume;
  • its own normal one-identity/one-presence lease;
  • its own task assignment and Beacon path (auto, PULSE, FLUX, ORBIT);
  • an authenticated private registration token known only to Customer Service.

The customer configures one main reward identity in the portal, but the portal does not trust a pasted public Client-ID. While logged into the game with the intended owner identity, the customer requests a HOSTED CODE (or runs hosted-code in the CLI). The game stores only the SHA-256 hash of this random one-time code; it expires after 10 minutes. Customer Service redeems the code over the private game control plane and receives the proven Client-ID. The P-256 private key never leaves the browser/CLI.

When a worker starts, it registers its own Client-ID with Customer Service, which installs a delegation on the private game control plane:

worker identity -> main reward identity

A winning task records both values:

  • winner_client_id = durable reward owner
  • winner_worker_client_id = cryptographic identity that actually submitted the winning guess

Thus several workers can run concurrently without disabling the game's normal single-presence rule. The original NFT is retrieved later by logging into the browser/CLI with the main reward identity.

Worker raw identity files can be downloaded or replaced from the portal. They contain a private key and must be treated as secrets. Replacing an identity stops the worker and forces private delegation registration again on next start. Deleting a worker also deletes its identity volume, so back it up first if it must remain portable.

Worker containment

Managed worker containers receive no Docker socket and no published ports. The reference Docker Engine request additionally uses:

  • read-only root filesystem
  • all Linux capabilities dropped
  • no-new-privileges
  • 256 MiB memory limit
  • 1 CPU limit
  • PID limit 128
  • only the private identity volume writable

The Customer Service itself needs Docker Engine control. Directly mounting /var/run/docker.sock effectively gives this service host-level Docker control. The provided Compose setup supports it for a simple self-hosted deployment, but a production setup should preferably put a narrowly permissioned Docker Socket Proxy in front and set DOCKER_HOST to that proxy instead.

Capacity is also bounded independently of reverse-proxy rate limiting. Stopped worker records do not allocate a Docker volume until the worker is actually started (or an identity is explicitly uploaded):

CS_MAX_WORKERS_PER_CUSTOMER=20
CS_MAX_WORKERS_GLOBAL=1000
CS_MAX_RUNNING_WORKERS_PER_CUSTOMER=10
CS_MAX_RUNNING_WORKERS_GLOBAL=100

Optional Beacon Hunt

Enable from the game Admin Runtime settings or defaults:

DEFAULT_BEACON_HUNT_ENABLED=1
DEFAULT_BEACON_BONUS_WEIGHT=2

Players/agents choose PULSE, FLUX or ORBIT before the current lottery window ends. Neural Hunt plans the first drand round strictly after that boundary. When it becomes available, SHA-256(signature) is used to derive both the boosted path and the weighted selection. A matching path receives the configured weight; a non-matching path still has weight 1.

Successful draws are stored in beacon_draws with beacon ID, round, signature, derived randomness, boosted path, ticket count and selected count, and the latest record is available from /api/public/beacon/<task-id>/latest for independent audit.

The current implementation relies on HTTPS to the configured drand endpoint and stores the returned signature for external verification. It does not yet perform local BLS signature verification against the drand chain public key. If cryptographic self-verification is a product requirement, add a vetted drand client/verifier before making that claim in customer-facing material.

V4.2.6 engagement credits and account controls

The Hosted Service has two optional customer-funded-by-game credit rewards. Both default to zero and are administered persistently from the private Customer Admin:

CS_NEW_CUSTOMER_CREDITS=0
CS_POSITIVE_TIP_CREDITS=0

CS_NEW_CUSTOMER_CREDITS is booked atomically with account creation. CS_POSITIVE_TIP_CREDITS is booked only for a delegated Hosted Worker when an accepted/evaluated guess creates a new personal best score. A ticket that is not selected by the lottery is not scored and earns no reward. Normal browser/CLI identities do not create these Customer-Service reward events.

The game persists each positive-tip event to an outbox and retries delivery to the private Customer Service (CUSTOMER_SERVICE_INTERNAL_URL, normally http://customer-service:8092 in Docker). Customer Service applies its current configured reward amount and uses an idempotent ledger reference, so temporary outages and retries neither lose nor duplicate credits.

When PayPal is disabled the public Customer Portal hides the purchase panel entirely. Its visible credit history is aggregated by UTC day/reason for readability; the raw ledger remains unchanged in the database.

The private Customer Admin can also block/unblock accounts, stop individual/all workers, disable new logins, disable new registrations, configure registration Proof-of-Work and optionally enforce time-limited one-shot invite codes. Blocking an account deletes its active sessions and revokes Worker leases immediately.

Worker Recovery und Image-Rollout (V4.2.9)

Hosted Worker erkennen stille/halb-offene Game-WebSockets nach spätestens etwa 90 Sekunden und verbinden sich mit Backoff neu. Ein abgelaufener Game-JWT wird mit derselben persistenten Worker-Identity automatisch erneuert. Auch ein noch nicht sofort verfügbarer Folge-Task wird wiederholt angefragt.

Neue Worker-Container werden mit Docker RestartPolicy=unless-stopped erzeugt. Bewusstes Stoppen über das Portal/Admin bleibt gestoppt. Die interne Worker-Registrierung akzeptiert nur Worker mit aktivem running/starting-Lease, sodass die Restart-Policy keine Sperre umgeht.

Im privaten Customer Admin kann das konfigurierte CS_WORKER_IMAGE explizit neu gepullt werden. Ein einzelner Worker oder alle Worker können anschließend mit dem neuen Image neu erzeugt werden. Named Identity Volumes werden dabei nicht entfernt; laufende Worker werden nach dem Update wieder gestartet, gestoppte bleiben gestoppt.

Distributed Service Controllers (V4.3)

Worker orchestration can now be separated from the commercial Customer Service. In controller mode, Customer Service keeps customer accounts, prepaid billing, PayPal, reward delegation, worker configuration and leases, while Docker lifecycle operations are sent to authenticated Service Controllers on remote worker hosts.

This means the Customer Service no longer needs docker.sock in controller-only deployments. Existing public Customer Portal endpoints and worker semantics remain unchanged. hybrid mode is provided as a migration path for already-created local worker identity volumes.

See SERVICE_CONTROLLER.md for the full topology and deployment settings.