# Deploy the isolated test broker on your server This package is ready for operator deployment. Compose, setup generation and Mosquitto configuration were checked locally. The user deployed the broker; server authentication/ACL/graceful-persistence checks and external TLS/plaintext MQTT exchanges passed on 2026-10-06. The user requested server deployment artifacts and an optional plaintext connection alongside TLS. Both listeners require test credentials. Topic ACLs are disabled for functional testing: every authenticated account can publish and subscribe to any topic. Plaintext sends credentials/messages without encryption; expose that test port only to intended test clients. ## Files to copy Copy the project's `deploy/` directory to a separate server directory, for example `/opt/robot-market-mqtt5-test`. Do not copy existing production credentials/configuration. The scripts require Linux, Docker Engine, Docker Compose, Python 3 and OpenSSL. All deployment commands use standard Linux tools. The official image is pinned to: ```text eclipse-mosquitto:2.1.2-alpine@sha256:38c0da4f2ef84284d47b3b3eeea1cb3bdeabe81ee10caf0cd5c5ff61ee3ea408 ``` This immutable multi-platform index includes Linux amd64, ARM64, ARMv6, 386, ppc64le and s390x. Confirm your server matches a supported platform. The originally proposed `2.1.2` tag is absent; `2.1.2-alpine` is the verified official tag. [Official image metadata](https://github.com/docker-library/official-images/blob/master/library/eclipse-mosquitto) Commands below run from the copied deployment directory, not the Go project root: ```sh cd /opt/robot-market-mqtt5-test docker compose version docker compose pull sudo python3 scripts/prepare.py --host mqtt-test.example.com ``` Replace `mqtt-test.example.com` with the exact server DNS name **or public IP** your clients will use. Do not include a scheme or port. The helper makes a dedicated private test CA and a 90-day server certificate with that name/IP plus localhost/127.0.0.1 SANs. A client connecting under a different name must fail hostname verification. Preparation generates random test passwords, hashes them using the pinned image's `mosquitto_passwd`, and never prints them or passes them in process arguments. It runs a network-disabled disposable hashing container; it does not start the broker. It refuses to overwrite `.env`, `certs/`, `secrets/` or `data/`. If it fails after partial generation, inspect privately and use a fresh dedicated directory rather than overwriting unknown state. Default runtime owner is numeric UID/GID 1883:1883. The helper sets broker-readable files to owner/group-restricted modes and gives the data directory to that UID. CA signing keys remain outside the broker mounts. Running preparation with sudo is needed to assign that ownership on a normal server. Rootless Docker/user-namespace mappings require deliberate permission adaptation; they have not been verified here. ## Select ports and start Edit the generated `.env` for available server ports. Default host ports intentionally avoid common production ports: | Transport | Default server port | Exposure | | --- | --- | --- | | MQTT over TLS | 8884 -> container 8883 | Base Compose file | | Plaintext MQTT | 1884 -> container 1883 | Only with insecure override | The base file publishes TLS on IPv4 `0.0.0.0`. To limit exposure, set TLS_BIND_IP to the appropriate server interface. The broker's internal plaintext listener is available to containers on its Docker network; the base file does not publish it on the server host. TLS only: ```sh docker compose config --quiet docker compose run --rm --no-deps broker --test-config -c /mosquitto/config/mosquitto.conf docker compose up -d ``` TLS plus plaintext, as requested: ```sh docker compose -f compose.yaml -f compose.insecure.yaml config --quiet docker compose -f compose.yaml -f compose.insecure.yaml run --rm --no-deps broker --test-config -c /mosquitto/config/mosquitto.conf docker compose -f compose.yaml -f compose.insecure.yaml up -d ``` Use the same file combination for subsequent operations. `config --quiet` avoids printing resolved configuration. Passwords are file-mounted, not Compose environment variables. All application payloads still need validation regardless of transport. Allow the chosen TLS port through the provider firewall/security group and server firewall. If using plaintext, allow 1884 only from intended test client IPs. Docker-published ports may bypass ordinary UFW rules; use provider rules or Docker-aware filtering (DOCKER-USER/nftables appropriate to your Docker setup) and verify from an external client. Do not edit existing broker listeners or expose 1883/8883 merely because they are common defaults. ## Credentials and topics Private generated files (read locally on the server, do not commit): | File | Purpose | | --- | --- | | secrets/clients/test-backend.json | Dedicated Go backend account | | secrets/clients/sim-001.json | Simulator/device test identity sim-001 | | secrets/clients/esp32-lab-001.json | Physical test identity esp32-lab-001 | | secrets/runtime/passwords | Hashed broker account database | | secrets/runtime/health.conf | Health-only account in protected CLI config | | certs/server/ca.crt | Public test CA to copy to clients | | certs/server/server.key | Broker private key; stays on server | | certs/authority/ca.key | Private CA signing key; not mounted into broker | The Go backend username is `test-backend`; its password is in its JSON handoff file. Device usernames match their test device IDs. **Functional test mode: any authenticated account can publish and subscribe to any topic.** Username and topic device IDs do not need to match. The Mosquitto ACL plugin is not loaded. No password/certificate regeneration is needed when switching an existing test deployment to this mode. Both MQTT 3.1.1 and MQTT 5 clients can connect. The browser observer subscribes to `robot-market/test/v1/#` and shows raw payloads without requiring a JSON schema, matching device ID or valid timestamp. Copy `ca.crt` and the backend credential handoff through your normal private file-transfer mechanism. When ready, provide the broker hostname/IP, selected TLS/plaintext ports and a path to the private backend credential file plus the public CA on your development computer. There is no need to paste passwords into chat. If you changed ports in `.env`, update the port metadata in the client JSON files or explicitly use the correct ports. For the existing C config, mqtt_server/port/user/pass/ca_cert come from these test settings. Wi-Fi remains device-specific. Select the actual C MQTT protocol/library separately. The Go backend/simulator support verified TLS and explicit test-only plaintext transport (mqtt.allow_plaintext=true or --allow-plaintext with a mqtt:// URL). See [RUNNING.md](RUNNING.md). TLS hostname/CA verification is never disabled. ## Verify after deployment Before attaching real devices, run: ```sh sudo python3 scripts/verify.py --restart ``` For the plaintext override: ```sh sudo python3 scripts/verify.py --insecure --restart ``` The verifier requires a running dedicated test broker. It uses temporary protected client config files and pinned-image CLI containers in that broker's network namespace, with no new host ports. It tests TLS health exchange, authorized device/backend flow, anonymous/bad-password/bad-CA/wrong-host rejection, unrestricted cross-device publication and reception. Optional flags check plaintext and graceful restart retained/session recovery. Commands do not expose real credentials as arguments/output. Run it on a quiet test instance: it temporarily sets and clears the retained status for esp32-lab-001 and, with --restart, restarts this broker. It never publishes retained commands. The current functional verifier expects cross-device traffic to succeed. Graceful restart is not crash-durability evidence. A tool failure is not an MQTT denial. The verifier checks MQTT 5 PUBACK reason codes for publishes: Mosquitto 2.1.2 can exit zero after reporting a rejected publish, so an exit code alone is insufficient. Successful publications must receive a non-error PUBACK; missing acknowledgements are inconclusive. The user ran the corrected verifier on 2026-10-06 against the deployed server; all server-container checks passed. External TLS/plaintext authenticated exchanges also passed from the development computer on 2026-10-06. The container health check independently publishes and receives a unique token over authenticated TLS and clears its retained health probe. It verifies listener/auth/ACL/message flow, not just process liveness. Inspect status and logs (add the insecure file when applicable): ```sh docker compose ps docker compose logs --tail 50 broker docker compose exec broker /bin/sh /usr/local/bin/mqtt-healthcheck docker compose port broker 8883 ``` Verify externally from the development computer as well: DNS/IP, firewall reachability, certificate chain and SAN, authentication, and actual MQTT exchange. The server-container verifier does not prove external routing. Record separate local-artifact, server-runtime and physical-hardware results. ## Stop, update, rollback and persistence ```sh docker compose stop broker docker compose start broker docker compose restart broker docker compose down ``` The broker saves session/retained data under `data/`. Graceful shutdown has 30 seconds; autosave runs every 30 seconds. Queue sizes, max clients, packet sizes and container resources are bounded. Docker stdout logs rotate at 5 MiB × 3 files. In-memory/broker counters are not per-device durable accounting. For a consistent backup, stop this test broker first, then archive `.env`, tracked configuration, `certs/`, `secrets/` and `data/` into a private backup location. Restart after backup. For example, from the deployment directory: ```sh docker compose stop broker sudo tar -czf /root/mqtt5-test-backup.tar.gz .env compose.yaml compose.insecure.yaml mosquitto.conf acl scripts certs secrets data sudo chmod 600 /root/mqtt5-test-backup.tar.gz docker compose start broker ``` Choose a unique backup name each time. Before an update, preserve the old Compose image reference and a consistent data snapshot. Change the pinned tag/digest intentionally, pull, run --test-config, then recreate only this broker and rerun verification. For rollback, stop the broker, restore the prior configuration/image and matching data snapshot in a dedicated directory, fix UID/GID modes, and start it. Do not assume newer persistence data can be opened by an older image. `down` keeps bind-mounted data. Deleting `data/` is an explicit reset and loses sessions/retained messages; do not delete production paths or add automatic destructive cleanup. ## Certificate renewal This package uses a private test CA, not automatic public ACME issuance. Store the signing key privately and track CA/server expiration. Renew before the 90-day server certificate expires. Do not rerun preparation over existing state; it deliberately refuses to rotate credentials/certificates implicitly. To renew a server certificate, generate a new key/CSR in a private staging directory, sign with the existing test CA using the SANs in server.ext, and verify chain plus hostname/IP. Keep old cert/key for rollback. Stop only this broker, install the new matching pair in certs/server with owner/group and 0640 permissions, start it, rerun TLS health and external client verification. Removing/replacing the whole directory while the container runs can leave old bind-mount references; stop/recreate when replacing directories. When CA trust changes, distribute the new public CA to clients before switching server trust. A public certificate can be integrated later with a deliberate health-check hostname/trust adjustment; public CAs do not issue localhost SANs, so simply replacing this private certificate can break the local TLS health check. No remote DNS, ACME method or renewal timer is assumed. ## Validation completed during preparation - Registry manifest/digest verified for the actual 2.1.2-alpine tag. - Base and TLS+plaintext Compose configurations: PASS (`config --quiet`). - Shell/Python syntax: PASS. - Setup helper with throwaway credentials/certificates in /tmp: PASS, hashed passwords produced without secret output. - Mosquitto 2.1.2 `--test-config` in network-disabled disposable container: PASS; plugins/configuration accepted, no broker started. - Server-container runtime TLS/plaintext/auth/ACL/graceful-restart checks: PASS, based on user-provided verifier output on 2026-10-06; external TLS/plaintext authenticated exchanges also passed. - External client connectivity: PASS, authenticated MQTT 5 device telemetry publish/backend receive on TLS 8884 (CA and hostname verification enabled) and plaintext 1884. Non-retained unique probes only. - Physical hardware: NOT VERIFIED.