From a3de71930c8feeb7c123bed9c769b50b21029e0b Mon Sep 17 00:00:00 2001 From: Andrew Mirsky Date: Wed, 28 May 2025 08:39:02 -0400 Subject: [PATCH] adding favicon, broker and client config documents. --- docs/assets/images/favicon.png | Bin 0 -> 20164 bytes docs/references/amqtt_pub.md | 4 +- docs/references/amqtt_sub.md | 19 ++--- docs/references/broker_config.md | 124 +++++++++++++++++++++++++++++++ docs/references/client.md | 8 +- docs/references/client_config.md | 68 +++++++++++++++++ samples/broker_simple.py | 21 ++++++ 7 files changed, 223 insertions(+), 21 deletions(-) create mode 100644 docs/assets/images/favicon.png create mode 100644 docs/references/broker_config.md create mode 100644 docs/references/client_config.md create mode 100644 samples/broker_simple.py diff --git a/docs/assets/images/favicon.png b/docs/assets/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..4355171c47347d37110de04606a10ed1ba84bd6e GIT binary patch literal 20164 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�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 Ef;AKJl>h($ literal 0 HcmV?d00001 diff --git a/docs/references/amqtt_pub.md b/docs/references/amqtt_pub.md index 1b84a20..65ab22d 100644 --- a/docs/references/amqtt_pub.md +++ b/docs/references/amqtt_pub.md @@ -44,7 +44,7 @@ Note that for simplicity, `amqtt_pub` uses mostly the same argument syntax as [m - `--will-retain` - If given, if the client disconnects unexpectedly the message sent out will be treated as a retained message. This must be used in conjunction with `--will-topic`. - `--extra-headers` - Specify a JSON object string with key-value pairs representing additional headers that are transmitted on the initial connection, but only when using a websocket connection -## Configuration +## Default Configuration Without the `-c` argument, the broker will run with the following, default configuration: @@ -57,8 +57,6 @@ Using the `-c` argument allows for configuration with a YAML structured file; se ## Examples -Examples below are adapted from [mosquitto_pub](http://mosquitto.org/man/mosquitto_pub-1.html) documentation. - Publish temperature information to localhost with QoS 1: ```bash diff --git a/docs/references/amqtt_sub.md b/docs/references/amqtt_sub.md index 9d5335d..37e1125 100644 --- a/docs/references/amqtt_sub.md +++ b/docs/references/amqtt_sub.md @@ -39,22 +39,19 @@ Note that for simplicity, `amqtt_sub` uses mostly the same argument syntax as [m - `--will-retain` - If given, if the client disconnects unexpectedly the message sent out will be treated as a retained message. This must be used in conjunction with `--will-topic`. - `--extra-headers` - Specify a JSON object string with key-value pairs representing additional headers that are transmitted on the initial connection, but only when using a websocket connection -## Configuration +## Default Configuration -If `-c` argument is given, `amqtt_sub` will read specific MQTT settings for the given configuration file. This file must be a valid [YAML](http://yaml.org/) file which may contain the following configuration elements: +Without the `-c` argument, the broker will run with the following, default configuration: + +```yaml +--8<-- "../amqtt/amqtt/scripts/default_client.yaml" +``` + +Using the `-c` argument allows for configuration with a YAML structured file; see [client configuration](client_config.md). -- `keep_alive`: Keep-alive timeout sent to the broker. Defaults to `10` seconds. -- `ping_delay`: Auto-ping delay before keep-alive timeout. Defaults to 1. Setting to `0` will disable to 0 and may lead to broker disconnection. -- `default_qos`: Default QoS for messages published. Defaults to 0. -- `default_retain`: Default retain value to messages published. Defaults to `false`. -- `auto_reconnect`: Enable or disable auto-reconnect if connection with the broker is interrupted. Defaults to `false`. -- `reconnect_retries`: Maximum reconnection retries. Defaults to `2`. Negative value will cause client to reconnect infinitely. -- `reconnect_max_interval`: Maximum interval between 2 connection retry. Defaults to `10`. ## Examples -Examples below are adapted from [mosquitto_sub](http://mosquitto.org/man/mosquitto_sub-1.html) documentation. - Subscribe with QoS 0 to all messages published under $SYS/: ```bash diff --git a/docs/references/broker_config.md b/docs/references/broker_config.md new file mode 100644 index 0000000..157679d --- /dev/null +++ b/docs/references/broker_config.md @@ -0,0 +1,124 @@ +# Broker Configuration + +This configuration structure is valid as a python dictionary passed to the `amqtt.broker.Broker` class's `__init__` method or +as a yaml formatted file passed to the `amqtt` script. + +### `listeners` *(list[mapping])* + +Defines the network listeners used by the service. Items defined in the `default` listener will be +applied to all other listeners, unless they are overridden by the configuration for the specific +listener. + +- `default` | ``: Named listener + - `type` *(string)*: Transport type. Can be `tcp` or `ws`. + - `bind` *(string)*: IP address and port (e.g., `0.0.0.0:1883`) + - `max-connections` *(integer)*: Maximum number of clients that can connect to this interface + - `ssl` *(string)*: Enable SSL connection. Can be `on` or `off` (default: off). + - `cafile` *(string)*: Path to a file of concatenated CA certificates in PEM format. See [Certificates](https://docs.python.org/3/library/ssl.html#ssl-certificates) for more info. + - `capath` *(string)*: Path to a directory containing several CA certificates in PEM format, following an [OpenSSL specific layout](https://docs.openssl.org/master/man3/SSL_CTX_load_verify_locations/). + - `cadata` *(string)*: Either an ASCII string of one or more PEM-encoded certificates or a bytes-like object of DER-encoded certificates. + - `certfile` *(string)*: Path to a single file in PEM format containing the certificate as well as any number of CA certificates needed to establish the certificate's authenticity. + - `keyfile` *(string): A file containing the private key. Otherwise the private key will be taken from `certfile` as well. + +### `sys_interval` *(int)* + +System status report interval in seconds (`broker_sys` plugin) + +### `timeout-disconnect-delay` *(int)* + +Client disconnect timeout without a keep-alive + + +### `auth` *(mapping)* + +Configuration for authentication behaviour: + +- `plugins` *(list[string])*: defines the list of plugins which are activated as authentication plugins. Note the plugins must be defined in the `amqtt.broker.plugins` [entry point](https://packaging.python.org/en/latest/guides/creating-and-discovering-plugins/#using-package-metadata). +- `allow-anonymous` *(bool)*: used by the internal `amqtt.plugins.authentication.AnonymousAuthPlugin` plugin. This parameter enables (`on`) or disable anonymous connection, i.e. connection without username. +- `password-file` *(string)*: used by the internal `amqtt.plugins.authentication.FileAuthPlugin` plugin. Path to file which includes `username:password` pair, one per line. The password should be encoded using sha-512 with `mkpasswd -m sha-512` or: + ```python + import sys + from getpass import getpass + from passlib.hash import sha512_crypt + + passwd = input() if not sys.stdin.isatty() else getpass() + print(sha512_crypt.hash(passwd)) + ``` + +### `topic-check` *(mapping)* + +Configuration for access control policies for publishing and subscribing to topics: + +- `enabled` *(bool)*: Enable access control policies (`true`). `false` will allow clients to publish and subscribe to any topic. +- `plugins` *(list[string])*: defines the list of plugins which are activated as access control plugins. Note the plugins must be defined in the `amqtt.broker.plugins` [entry point](https://pythonhosted.org/setuptools/setuptools.html#dynamic-discovery-of-services-and-plugins). +- `acl` *(list)*: used by the internal `amqtt.plugins.topic_acl.TopicAclPlugin` plugin to determine subscription access. This parameter defines the list of access control rules; each item is a key-value pair, where: +`:[, , ...]` *(string, list[string])*: username of the client followed by a list of allowed topics (wildcards are supported: `#`, `+`). +use `anonymous` username for the list of allowed topics if using the `auth_anonymous` plugin. +- `publish-acl` *(list)*: used by the internal `amqtt.plugins.topic_acl.TopicAclPlugin` plugin to determine publish access. This parameter defines the list of access control rules; each item is a key-value pair, where: +`:[, , ...]` *(string, list[string])*: username of the client followed by a list of allowed topics (wildcards are supported: `#`, `+`). +use `anonymous` username for the list of allowed topics if using the `auth_anonymous` plugin. + + + +## Default Configuration + +```yaml +--8<-- "../amqtt/amqtt/scripts/default_broker.yaml" +``` + +## Example + +```yaml +listeners: + default: + max-connections: 500 + type: tcp + my-tcp-1: + bind: 127.0.0.1:1883 + my-tcp-2: + bind: 1.2.3.4:1884 + max-connections: 1000 + my-tcp-ssl-1: + bind: 127.0.0.1:8885 + ssl: on + cafile: /some/cafile + capath: /some/folder + capath: certificate data + certfile: /some/certfile + keyfile: /some/key + my-ws-1: + bind: 0.0.0.0:8080 + type: ws + my-wss-1: + bind: 0.0.0.0:9003 + type: ws + ssl: on + certfile: /some/certfile + keyfile: /some/key +timeout-disconnect-delay: 2 +auth: + plugins: ['auth_anonymous', 'auth_file'] + allow-anonymous: true + password-file: /some/password-file +topic-check: + enabled: true + plugins: ['topic_acl'] + acl: + username1: ['repositories/+/master', 'calendar/#', 'data/memes'] + username2: [ 'calendar/2025/#', 'data/memes'] + anonymous: ['calendar/2025/#'] +``` + +This configuration file would create the following listeners: + +- `my-tcp-1`: an unsecured TCP listener on port 1883 allowing `500` clients connections simultaneously +- `my-tcp-2`: an unsecured TCP listener on port 1884 allowing `1000` client connections +- `my-tcp-ssl-1`: a secured TCP listener on port 8883 allowing `500` clients connections simultaneously +- `my-ws-1`: an unsecured websocket listener on port 9001 allowing `500` clients connections simultaneously +- `my-wss-1`: a secured websocket listener on port 9003 allowing `500` + +And enable the following access controls: + +- `username1` to login and subscribe/publish to topics `repositories/+/master`, `calendar/#` and `data/memes` +- `username2` to login and subscribe/publish to topics `calendar/2025/#` and `data/memes` +- any user not providing credentials (`anonymous`) can only subscribe/publish to `calendar/2025/#` diff --git a/docs/references/client.md b/docs/references/client.md index 923e899..74cc887 100644 --- a/docs/references/client.md +++ b/docs/references/client.md @@ -136,14 +136,8 @@ The `MQTTClient` class's `__init__` method accepts a `config` parameter which al Details on the `config` parameter structure is a dictionary whose structure is identical to yaml formatted file[^1] used by the included broker script: [client configuration](client_config.md) - - -::: amqtt.broker.Broker +::: amqtt.client.MQTTClient [^1]: See [PyYAML](http://pyyaml.org/wiki/PyYAMLDocumentation) for loading YAML files as Python dict. - - - -::: amqtt.client.MQTTClient diff --git a/docs/references/client_config.md b/docs/references/client_config.md new file mode 100644 index 0000000..3616b1c --- /dev/null +++ b/docs/references/client_config.md @@ -0,0 +1,68 @@ +# Client Configuration + +This configuration structure is valid as a python dictionary passed to the `amqtt.broker.MQTTClient` class's `__init__` method or +as a yaml formatted file passed to the `amqtt_pub` script. + +### `keep_alive` *(int)* + +Keep-alive timeout sent to the broker. Defaults to `10` seconds. + +### `ping_delay` *(int)* + +Auto-ping delay before keep-alive timeout. Defaults to 1. Setting to `0` will disable to 0 and may lead to broker disconnection. + +### `default_qos` *(int: 0-2)* + +Default QoS for messages published. Defaults to 0. + + +### `default_retain` *(bool)* + +Default retain value to messages published. Defaults to `false`. + + +### `auto_reconnect` *(bool)* + +Enable or disable auto-reconnect if connection with the broker is interrupted. Defaults to `false`. + +### `reconnect_retries` *(int)* + +Maximum reconnection retries. Defaults to `2`. Negative value will cause client to reconnect infinitely. + +### `reconnect_max_interval` *(int)* + +Maximum interval between 2 connection retry. Defaults to `10`. + +### `topics` *(list[mapping])* + +Specify the topics and what flags should be set for messages published to them. + +- ``: Named listener + - `qos` *(int, 0-3)*: + - `retain` *(bool)*: + + +## Default Configuration + +```yaml +--8<-- "../amqtt/amqtt/scripts/default_client.yaml" +``` + +## Example + +```yaml + +keep_alive: 10 +ping_delay: 1 +default_qos': 0 +default_retain: false +auto_reconnect: true +reconnect_max_interval: 5, +reconnect_retries: 10 +topics: + - test: + qos: 0 + - some_topic: + qos: 2 + retain: true +``` diff --git a/samples/broker_simple.py b/samples/broker_simple.py new file mode 100644 index 0000000..22c4639 --- /dev/null +++ b/samples/broker_simple.py @@ -0,0 +1,21 @@ +import asyncio +import logging + +from amqtt.broker import Broker + +broker = Broker() + + +async def test_coro() -> None: + await broker.start() + + +if __name__ == "__main__": + formatter = "[%(asctime)s] :: %(levelname)s :: %(name)s :: %(message)s" + logging.basicConfig(level=logging.INFO, format=formatter) + + asyncio.get_event_loop().run_until_complete(test_coro()) + try: + asyncio.get_event_loop().run_forever() + except KeyboardInterrupt: + asyncio.get_event_loop().run_until_complete(broker.shutdown())