Files
Ryan 71ef622b4f
Test and deploy MQTT backend / test (push) Successful in 2m8s
Test and deploy MQTT backend / deploy (push) Successful in 18s
Deploy MQTT test backend with private viewer and Gitea CI/CD
2026-10-08 14:48:53 +03:30

163 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.