From cdb13035eaaf8259dffa4d0c46766a91d0fe26b5 Mon Sep 17 00:00:00 2001 From: kerwincui <164770707@qq.com> Date: Sun, 20 Mar 2022 23:57:07 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0docker=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/Dockerfile.txt | 25 +- docker/description.txt | 17 - docker/emqx4.0/emqx.conf | 2061 ++++++++++++++++++++++++++++ docker/emqx4.0/emqx_auth_http.conf | 133 ++ docker/emqx4.0/emqx_web_hook.conf | 22 + docker/nginx.conf | 47 + document/message.png | Bin 0 -> 42991 bytes 7 files changed, 2280 insertions(+), 25 deletions(-) create mode 100644 docker/emqx4.0/emqx.conf create mode 100644 docker/emqx4.0/emqx_auth_http.conf create mode 100644 docker/emqx4.0/emqx_web_hook.conf create mode 100644 docker/nginx.conf create mode 100644 document/message.png diff --git a/docker/Dockerfile.txt b/docker/Dockerfile.txt index 066f839b..59b9807e 100644 --- a/docker/Dockerfile.txt +++ b/docker/Dockerfile.txt @@ -4,21 +4,21 @@ ENV VERSION 1.1 ENV AUTHOR kerwincui ENV INFO wumei smart open source living iot platform -# 安装工具和设置时区 apt install curl -y --no-install-recommends && \ -RUN apt-get update && \ - apt-get install vim -y --no-install-recommends && \ +# 安装工具和设置时区 apt install curl -y --no-install-recommends && \ apt-get install vim -y --no-install-recommends && \ +RUN apt-get update && \ apt-get install wget -y --no-install-recommends && \ ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + #更换国内源 RUN wget http://qiniu.xiwen.online/Debian10.list && \ mv Debian10.list /etc/apt/sources.list && \ apt update && apt upgrade -y -# 安装nginx和redis,需要容器内配置nginx和redis +# 安装nginx和redis RUN apt-get install nginx -y --no-install-recommends && \ apt-get install redis-server -y --no-install-recommends -# 安装 emqx,需要修改密码 +# 安装 emqx RUN apt update && apt install -y \ apt-transport-https \ ca-certificates \ @@ -31,7 +31,16 @@ RUN apt update && apt install -y \ ./bionic \ stable" && \ apt update && \ - apt install emqx -y --no-install-recommends + apt install emqx=4.0.0 -y --no-install-recommends + +# 修改redis配置 +RUN sed -i "s/# requirepass foobared/requirepass wumei/g" /etc/redis/redis.conf + +# 复制emqx和Nginx的配置文件 +COPY ./emqx4.0/emqx.conf /etc/emqx/emqx.conf +COPY ./emqx4.0/emqx_auth_http.conf /etc/emqx/plugins/emqx_auth_http.conf +COPY ./emqx4.0/emqx_web_hook.conf /etc/emqx/plugins/emqx_web_hook.conf +COPY ./nginx.conf /etc/nginx/nginx.conf # 挂载卷 VOLUME /var/wumei-smart @@ -44,5 +53,5 @@ ENTRYPOINT ["/docker-entrypoint.sh"] COPY ./wumei-smart.tar /var/ RUN cd /var && tar -xvf wumei-smart.tar && rm wumei-smart.tar -# 映射端口 -EXPOSE 80 3306 1883 18083 \ No newline at end of file +# 映射端口,可选:8081、8883、18083、8083 +EXPOSE 80 1883 8083 \ No newline at end of file diff --git a/docker/description.txt b/docker/description.txt index cf781486..a821dfc8 100644 --- a/docker/description.txt +++ b/docker/description.txt @@ -42,23 +42,6 @@ kerwincui/wumei-smart:1.0 ########################容器内处理########################## -# 安装mysql -### wget http://repo.mysql.com/mysql-apt-config_0.8.13-1_all.deb && \ - apt install ./mysql-apt-config_0.8.13-1_all.deb && \ - apt update && \ - apt install mysql-server -y -### service mysql start -### mysql_secure_installation -# 配置远程访问 -### vim /etc/mysql/mysql.conf.d/mysqld.cnf ,注释bind-address -### 本地登录mysql并更新用户host,并刷新 -### update mysql.user set host='%' where user='root'; flush privileges; -# 查看mysql字符集编码 -### show variables like 'character%'; -# 修改编码 -### vim /etc/mysql/mysql.conf.d/mysqld.cnf ,增加character-set-server=utf8 -### vim /etc/mysql/conf.d/mysql.cnf ,增加default-character-set=utf8 -# 导入sql文件 # 配置redis # vim /etc/redis/redis.conf ,取消注释requirepass admin123,注释 bind:127.0.0.1 diff --git a/docker/emqx4.0/emqx.conf b/docker/emqx4.0/emqx.conf new file mode 100644 index 00000000..fe44c8eb --- /dev/null +++ b/docker/emqx4.0/emqx.conf @@ -0,0 +1,2061 @@ +##==================================================================== +## EMQ X Configuration R4.0 +##==================================================================== + +##-------------------------------------------------------------------- +## Cluster +##-------------------------------------------------------------------- + +## Cluster name. +## +## Value: String +cluster.name = emqxcl + +## Specify the erlang distributed protocol. +## +## Value: Enum +## - inet_tcp: the default; handles TCP streams with IPv4 addressing. +## - inet6_tcp: handles TCP with IPv6 addressing. +## - inet_tls: using TLS for Erlang Distribution. +## +## vm.args: -proto_dist inet_tcp +cluster.proto_dist = inet_tcp + +## Cluster auto-discovery strategy. +## +## Value: Enum +## - manual: Manual join command +## - static: Static node list +## - mcast: IP Multicast +## - dns: DNS A Record +## - etcd: etcd +## - k8s: Kubernates +## +## Default: manual +cluster.discovery = manual + +## Enable cluster autoheal from network partition. +## +## Value: on | off +## +## Default: on +cluster.autoheal = on + +## Autoclean down node. A down node will be removed from the cluster +## if this value > 0. +## +## Value: Duration +## -h: hour, e.g. '2h' for 2 hours +## -m: minute, e.g. '5m' for 5 minutes +## -s: second, e.g. '30s' for 30 seconds +## +## Default: 5m +cluster.autoclean = 5m + +##-------------------------------------------------------------------- +## Cluster using static node list + +## Node list of the cluster. +## +## Value: String +## cluster.static.seeds = emqx1@127.0.0.1,emqx2@127.0.0.1 + +##-------------------------------------------------------------------- +## Cluster using IP Multicast. + +## IP Multicast Address. +## +## Value: IP Address +## cluster.mcast.addr = 239.192.0.1 + +## Multicast Ports. +## +## Value: Port List +## cluster.mcast.ports = 4369,4370 + +## Multicast Iface. +## +## Value: Iface Address +## +## Default: 0.0.0.0 +## cluster.mcast.iface = 0.0.0.0 + +## Multicast Ttl. +## +## Value: 0-255 +## cluster.mcast.ttl = 255 + +## Multicast loop. +## +## Value: on | off +## cluster.mcast.loop = on + +##-------------------------------------------------------------------- +## Cluster using DNS A records. + +## DNS name. +## +## Value: String +## cluster.dns.name = localhost + +## The App name is used to build 'node.name' with IP address. +## +## Value: String +## cluster.dns.app = emqx + +##-------------------------------------------------------------------- +## Cluster using etcd + +## Etcd server list, seperated by ','. +## +## Value: String +## cluster.etcd.server = http://127.0.0.1:2379 + +## The prefix helps build nodes path in etcd. Each node in the cluster +## will create a path in etcd: v2/keys/// +## +## Value: String +## cluster.etcd.prefix = emqxcl + +## The TTL for node's path in etcd. +## +## Value: Duration +## +## Default: 1m, 1 minute +## cluster.etcd.node_ttl = 1m + +## Path to a file containing the client's private PEM-encoded key. +## +## Value: File +## cluster.etcd.ssl.keyfile = etc/certs/client-key.pem + +## The path to a file containing the client's certificate. +## +## Value: File +## cluster.etcd.ssl.certfile = etc/certs/client.pem + +## Path to the file containing PEM-encoded CA certificates. The CA certificates +## are used during server authentication and when building the client certificate chain. +## +## Value: File +## cluster.etcd.ssl.cacertfile = etc/certs/ca.pem + +##-------------------------------------------------------------------- +## Cluster using Kubernates + +## Kubernates API server list, seperated by ','. +## +## Value: String +## cluster.k8s.apiserver = http://10.110.111.204:8080 + +## The service name helps lookup EMQ nodes in the cluster. +## +## Value: String +## cluster.k8s.service_name = emqx + +## The address type is used to extract host from k8s service. +## +## Value: ip | dns | hostname +## cluster.k8s.address_type = ip + +## The app name helps build 'node.name'. +## +## Value: String +## cluster.k8s.app_name = emqx + +## The suffix added to dns and hostname get from k8s service +## +## Value: String +## cluster.k8s.suffix = pod.cluster.local + +## Kubernates Namespace +## +## Value: String +## cluster.k8s.namespace = default + +##-------------------------------------------------------------------- +## Node +##-------------------------------------------------------------------- + +## Node name. +## +## See: http://erlang.org/doc/reference_manual/distributed.html +## +## Value: @ +## +## Default: emqx@127.0.0.1 +node.name = emqx@127.0.0.1 + +## Cookie for distributed node communication. +## +## Value: String +node.cookie = emqxsecretcookie + +## Data dir for the node +## +## Value: Folder +node.data_dir = data + +## Heartbeat monitoring of an Erlang runtime system. Comment the line to disable +## heartbeat, or set the value as 'on' +## +## Value: on +## +## vm.args: -heart +## node.heartbeat = on + +## Sets the number of threads in async thread pool. Valid range is 0-1024. +## +## See: http://erlang.org/doc/man/erl.html +## +## Value: 0-1024 +## +## vm.args: +A Number +node.async_threads = 32 + +## Sets the maximum number of simultaneously existing processes for this +## system if a Number is passed as value. +## +## See: http://erlang.org/doc/man/erl.html +## +## Value: Number [1024-134217727] +## +## vm.args: +P Number +node.process_limit = 2048000 + +## Sets the maximum number of simultaneously existing ports for this system. +## +## See: http://erlang.org/doc/man/erl.html +## +## Value: Number [1024-134217727] +## +## vm.args: +Q Number +node.max_ports = 1024000 + +## Set the distribution buffer busy limit (dist_buf_busy_limit). +## +## See: http://erlang.org/doc/man/erl.html +## +## Value: Number [1KB-2GB] +## +## vm.args: +zdbbl size +node.dist_buffer_size = 8MB + +## Sets the maximum number of ETS tables. Note that mnesia and SSL will +## create temporary ETS tables. +## +## Value: Number +## +## vm.args: +e Number +node.max_ets_tables = 256000 + +## Tweak GC to run more often. +## +## Value: Number [0-65535] +## +## vm.args: -env ERL_FULLSWEEP_AFTER Number +node.fullsweep_after = 1000 + +## Crash dump log file. +## +## Value: Log file +node.crash_dump = log/crash.dump + +## Specify SSL Options in the file if using SSL for Erlang Distribution. +## +## Value: File +## +## vm.args: -ssl_dist_optfile +## node.ssl_dist_optfile = etc/ssl_dist.conf + +## Sets the net_kernel tick time. TickTime is specified in seconds. +## Notice that all communicating nodes are to have the same TickTime +## value specified. +## +## See: http://www.erlang.org/doc/man/kernel_app.html#net_ticktime +## +## Value: Number +## +## vm.args: -kernel net_ticktime Number +node.dist_net_ticktime = 60 + +## Sets the port range for the listener socket of a distributed Erlang node. +## Note that if there are firewalls between clustered nodes, this port segment +## for nodes’ communication should be allowed. +## +## See: http://www.erlang.org/doc/man/kernel_app.html +## +## Value: Port [1024-65535] +node.dist_listen_min = 6369 +node.dist_listen_max = 6369 + +##-------------------------------------------------------------------- +## RPC +##-------------------------------------------------------------------- +## RPC Mode. +## +## Value: sync | async +rpc.mode = async + +## Max batch size of async RPC requests. +## +## Value: Integer +## Zero or negative value disables rpc batching. +## +## NOTE: RPC batch won't work when rpc.mode = sync +rpc.async_batch_size = 256 + +## TCP server port for RPC. +## +## Value: Port [1024-65535] +rpc.tcp_server_port = 5369 + +## TCP port for outgoing RPC connections. +## +## Value: Port [1024-65535] +rpc.tcp_client_port = 5369 + +## Number of utgoing RPC connections. +## +## Value: Interger [1-256] +rpc.tcp_client_num = 32 + +## RCP Client connect timeout. +## +## Value: Seconds +rpc.connect_timeout = 5s + +## TCP send timeout of RPC client and server. +## +## Value: Seconds +rpc.send_timeout = 5s + +## Authentication timeout +## +## Value: Seconds +rpc.authentication_timeout = 5s + +## Default receive timeout for call() functions +## +## Value: Seconds +rpc.call_receive_timeout = 15s + +## Socket idle keepalive. +## +## Value: Seconds +rpc.socket_keepalive_idle = 900s + +## TCP Keepalive probes interval. +## +## Value: Seconds +rpc.socket_keepalive_interval = 75s + +## Probes lost to close the connection +## +## Value: Integer +rpc.socket_keepalive_count = 9 + +## Size of TCP send buffer. +## +## Value: Bytes +rpc.socket_sndbuf = 1MB + +## Size of TCP receive buffer. +## +## Value: Seconds +rpc.socket_recbuf = 1MB + +## Size of user-level software socket buffer. +## +## Value: Seconds +rpc.socket_buffer = 1MB + +##-------------------------------------------------------------------- +## Log +##-------------------------------------------------------------------- + +## Where to emit the logs. +## Enable the console (standard output) logs. +## +## Value: off | file | console | both +## - off: disable logs entirely +## - file: write logs only to file +## - console: write logs only to standard I/O +## - both: write logs both to file and standard I/O +log.to = both + +## The log severity level. +## +## Value: debug | info | notice | warning | error | critical | alert | emergency +## +## Note: Only the messages with severity level higher than or equal to +## this level will be logged. +## +## Default: warning +log.level = warning + +## The dir for log files. +## +## Value: Folder +log.dir = log + +## The log filename for logs of level specified in "log.level". +## +## Value: String +## Default: emqx.log +log.file = emqx.log + +## Limits the total number of characters printed for each log event. +## +## Value: Integer +## Default: No Limit +#log.chars_limit = 8192 + +## Maximum size of each log file. +## +## Value: Number +## Default: 10M +## Supported Unit: KB | MB | GB +log.rotation.size = 10MB + +## Maximum rotation count of log files. +## +## Value: Number +## Default: 5 +log.rotation.count = 5 + +## To create additional log files for specific log levels. +## +## Value: File Name +## Format: log.$level.file = $filename, +## where "$level" can be one of: debug, info, notice, warning, +## error, critical, alert, emergency +## Note: Log files for a specific log level will only contain all the logs +## that higher than or equal to that level +## +#log.info.file = info.log +#log.error.file = error.log + +##-------------------------------------------------------------------- +## Authentication/Access Control +##-------------------------------------------------------------------- + +## Allow anonymous authentication by default if no auth plugins loaded. +## Notice: Disable the option in production deployment! +## +## Value: true | false +allow_anonymous = false + +## Allow or deny if no ACL rules matched. +## +## Value: allow | deny +acl_nomatch = allow + +## Default ACL File. +## +## Value: File Name +acl_file = etc/acl.conf + +## Whether to enable ACL cache. +## +## If enabled, ACLs roles for each client will be cached in the memory +## +## Value: on | off +enable_acl_cache = on + +## The maximum count of ACL entries can be cached for a client. +## +## Value: Integer greater than 0 +## Default: 32 +acl_cache_max_size = 32 + +## The time after which an ACL cache entry will be deleted +## +## Value: Duration +## Default: 1 minute +acl_cache_ttl = 1m + +## The action when acl check reject current operation +## +## Value: ignore | disconnect +## Default: ignore +acl_deny_action = ignore + +## Specify the global flapping detect policy. +## The value is a string composed of flapping threshold, duration and banned interval. +## 1. threshold: an integer to specfify the disconnected times of a MQTT Client; +## 2. duration: the time window for flapping detect; +## 3. banned interval: the banned interval if a flapping is detected. +## +## Value: Integer,Duration,Duration +flapping_detect_policy = 30, 1m, 5m + +##-------------------------------------------------------------------- +## MQTT Protocol +##-------------------------------------------------------------------- + +## Maximum MQTT packet size allowed. +## +## Value: Bytes +## Default: 1MB +mqtt.max_packet_size = 1MB + +## Maximum length of MQTT clientId allowed. +## +## Value: Number [23-65535] +mqtt.max_clientid_len = 65535 + +## Maximum topic levels allowed. 0 means no limit. +## +## Value: Number +mqtt.max_topic_levels = 0 + +## Maximum QoS allowed. +## +## Value: 0 | 1 | 2 +mqtt.max_qos_allowed = 2 + +## Maximum Topic Alias, 0 means no topic alias supported. +## +## Value: 0-65535 +mqtt.max_topic_alias = 65535 + +## Whether the Server supports MQTT retained messages. +## +## Value: boolean +mqtt.retain_available = true + +## Whether the Server supports MQTT Wildcard Subscriptions +## +## Value: boolean +mqtt.wildcard_subscription = true + +## Whether the Server supports MQTT Shared Subscriptions. +## +## Value: boolean +mqtt.shared_subscription = true + +## Whether to ignore loop delivery of messages.(for mqtt v3.1.1) +## +## Value: true | false +mqtt.ignore_loop_deliver = false + +## Whether to parse the MQTT frame in strict mode +## +## Value: true | false +mqtt.strict_mode = false + +##-------------------------------------------------------------------- +## Zones +##-------------------------------------------------------------------- + +##-------------------------------------------------------------------- +## External Zone + +## Idle timeout of the external MQTT connections. +## +## Value: duration +zone.external.idle_timeout = 15s + +## Hibernate after a duration of idle state. +## +## Value: duration +zone.external.hibernate_after = 60s + +## Publish limit for the external MQTT connections. +## +## Value: Number,Duration +## Example: 100 messages per 10 seconds. +## zone.external.publish_limit = 100,10s + +## Enable ACL check. +## +## Value: Flag +zone.external.enable_acl = on + +## Enable ban check. +## +## Value: Flag +zone.external.enable_ban = on + +## Enable per connection statistics. +## +## Value: on | off +zone.external.enable_stats = on + +## The action when acl check reject current operation +## +## Value: ignore | disconnect +## Default: ignore +zone.external.acl_deny_action = ignore + +## Force MQTT connection/session process GC after this number of +## messages | bytes passed through. +## +## Numbers delimited by `|'. Zero or negative is to disable. +zone.external.force_gc_policy = 1000|1MB + +## Max message queue length and total heap size to force shutdown +## connection/session process. +## Message queue here is the Erlang process mailbox, but not the number +## of queued MQTT messages of QoS 1 and 2. +## +## Numbers delimited by `|'. Zero or negative is to disable. +## +## Default: +## - 10000|32MB on ARCH_64 system +## - 10000|16MB on ARCH_32 sytem +## zone.external.force_shutdown_policy = 10000|32MB + +## Maximum MQTT packet size allowed. +## +## Value: Bytes +## Default: 1MB +## zone.external.max_packet_size = 64KB + +## Maximum length of MQTT clientId allowed. +## +## Value: Number [23-65535] +## zone.external.max_clientid_len = 1024 + +## Maximum topic levels allowed. 0 means no limit. +## +## Value: Number +## zone.external.max_topic_levels = 7 + +## Maximum QoS allowed. +## +## Value: 0 | 1 | 2 +## zone.external.max_qos_allowed = 2 + +## Maximum Topic Alias, 0 means no limit. +## +## Value: 0-65535 +## zone.external.max_topic_alias = 65535 + +## Whether the Server supports retained messages. +## +## Value: boolean +## zone.external.retain_available = true + +## Whether the Server supports Wildcard Subscriptions +## +## Value: boolean +## zone.external.wildcard_subscription = false + +## Whether the Server supports Shared Subscriptions +## +## Value: boolean +## zone.external.shared_subscription = false + +## Server Keep Alive +## +## Value: Number +## zone.external.server_keepalive = 0 + +## The backoff for MQTT keepalive timeout. The broker will kick a connection out +## until 'Keepalive * backoff * 2' timeout. +## +## Value: Float > 0.5 +zone.external.keepalive_backoff = 0.75 + +## Maximum number of subscriptions allowed, 0 means no limit. +## +## Value: Number +zone.external.max_subscriptions = 0 + +## Force to upgrade QoS according to subscription. +## +## Value: on | off +zone.external.upgrade_qos = off + +## Maximum size of the Inflight Window storing QoS1/2 messages delivered but unacked. +## +## Value: Number +zone.external.max_inflight = 32 + +## Retry interval for QoS1/2 message delivering. +## +## Value: Duration +zone.external.retry_interval = 30s + +## Maximum QoS2 packets (Client -> Broker) awaiting PUBREL, 0 means no limit. +## +## Value: Number +zone.external.max_awaiting_rel = 100 + +## The QoS2 messages (Client -> Broker) will be dropped if awaiting PUBREL timeout. +## +## Value: Duration +zone.external.await_rel_timeout = 300s + +## Default session expiry interval for MQTT V3.1.1 connections. +## +## Value: Duration +## -d: day +## -h: hour +## -m: minute +## -s: second +## +## Default: 2h, 2 hours +zone.external.session_expiry_interval = 2h + +## Maximum queue length. Enqueued messages when persistent client disconnected, +## or inflight window is full. 0 means no limit. +## +## Value: Number >= 0 +zone.external.max_mqueue_len = 1000 + +## Topic priorities. +## 'none' to indicate no priority table (by default), hence all messages +## are treated equal +## +## Priority number [1-255] +## Example: topic/1=10,topic/2=8 +## NOTE: comma and equal signs are not allowed for priority topic names +## NOTE: messages for topics not in the priority table are treated as +## either highest or lowest priority depending on the configured +## value for mqueue_default_priority +## +zone.external.mqueue_priorities = none + +## Default to highest priority for topics not matching priority table +## +## Value: highest | lowest +zone.external.mqueue_default_priority = highest + +## Whether to enqueue QoS0 messages. +## +## Value: false | true +zone.external.mqueue_store_qos0 = true + +## Whether to turn on flapping detect +## +## Value: on | off +zone.external.enable_flapping_detect = off + +## All the topics will be prefixed with the mountpoint path if this option is enabled. +## +## Variables in mountpoint path: +## - %c: clientid +## - %u: username +## +## Value: String +## zone.external.mountpoint = devicebound/ + +## Whether use username replace client id +## +## Value: boolean +## Default: false +zone.external.use_username_as_clientid = false + +## Whether to ignore loop delivery of messages.(for mqtt v3.1.1) +## +## Value: true | false +zone.external.ignore_loop_deliver = false + +## Whether to parse the MQTT frame in strict mode +## +## Value: true | false +zone.external.strict_mode = false + +##-------------------------------------------------------------------- +## Internal Zone + +zone.internal.allow_anonymous = true + +## Enable per connection stats. +## +## Value: Flag +zone.internal.enable_stats = on + +## Enable ACL check. +## +## Value: Flag +zone.internal.enable_acl = off + +## The action when acl check reject current operation +## +## Value: ignore | disconnect +## Default: ignore +zone.internal.acl_deny_action = ignore + +## See zone.$name.wildcard_subscription. +## +## Value: boolean +## zone.internal.wildcard_subscription = true + +## See zone.$name.shared_subscription. +## +## Value: boolean +## zone.internal.shared_subscription = true + +## See zone.$name.max_subscriptions. +## +## Value: Integer +zone.internal.max_subscriptions = 0 + +## See zone.$name.max_inflight +## +## Value: Number +zone.internal.max_inflight = 128 + +## See zone.$name.max_awaiting_rel +## +## Value: Number +zone.internal.max_awaiting_rel = 1000 + +## See zone.$name.max_mqueue_len +## +## Value: Number >= 0 +zone.internal.max_mqueue_len = 10000 + +## Whether to enqueue Qos0 messages. +## +## Value: false | true +zone.internal.mqueue_store_qos0 = true + +## Whether to turn on flapping detect +## +## Value: on | off +zone.internal.enable_flapping_detect = off + +## See zone.$name.force_shutdown_policy +## +## Default: +## - 10000|32MB on ARCH_64 system +## - 10000|16MB on ARCH_32 sytem +zone.internal.force_shutdown_policy = 100000|64MB + +## All the topics will be prefixed with the mountpoint path if this option is enabled. +## +## Variables in mountpoint path: +## - %c: clientid +## - %u: username +## +## Value: String +## zone.internal.mountpoint = cloudbound/ + +## Whether to ignore loop delivery of messages.(for mqtt v3.1.1) +## +## Value: true | false +zone.internal.ignore_loop_deliver = false + +## Whether to parse the MQTT frame in strict mode +## +## Value: true | false +zone.internal.strict_mode = false + +##-------------------------------------------------------------------- +## Listeners +##-------------------------------------------------------------------- + +##-------------------------------------------------------------------- +## MQTT/TCP - External TCP Listener for MQTT Protocol + +## listener.tcp.$name is the IP address and port that the MQTT/TCP +## listener will bind. +## +## Value: IP:Port | Port +## +## Examples: 1883, 127.0.0.1:1883, ::1:1883 +listener.tcp.external = 0.0.0.0:1883 + +## The acceptor pool for external MQTT/TCP listener. +## +## Value: Number +listener.tcp.external.acceptors = 8 + +## Maximum number of concurrent MQTT/TCP connections. +## +## Value: Number +listener.tcp.external.max_connections = 1024000 + +## Maximum external connections per second. +## +## Value: Number +listener.tcp.external.max_conn_rate = 1000 + +## Specify the {active, N} option for the external MQTT/TCP Socket. +## +## Value: Number +listener.tcp.external.active_n = 100 + +## Zone of the external MQTT/TCP listener belonged to. +## +## See: zone.$name.* +## +## Value: String +listener.tcp.external.zone = external + +## Rate limit for the external MQTT/TCP connections. Format is 'limit,duration'. +## +## Value: limit,duration +## Default: 100KB incoming per 10 seconds. +## listener.tcp.external.rate_limit = 100KB,10s + +## The access control rules for the MQTT/TCP listener. +## +## See: https://github.com/emqtt/esockd#allowdeny +## +## Value: ACL Rule +## +## Example: allow 192.168.0.0/24 +listener.tcp.external.access.1 = allow all + +## Enable the Proxy Protocol V1/2 if the EMQ X cluster is deployed +## behind HAProxy or Nginx. +## +## See: https://www.haproxy.com/blog/haproxy/proxy-protocol/ +## +## Value: on | off +## listener.tcp.external.proxy_protocol = on + +## Sets the timeout for proxy protocol. EMQ X will close the TCP connection +## if no proxy protocol packet recevied within the timeout. +## +## Value: Duration +## listener.tcp.external.proxy_protocol_timeout = 3s + +## Enable the option for X.509 certificate based authentication. +## EMQX will use the common name of certificate as MQTT username. +## +## Value: cn | dn | crt +## listener.tcp.external.peer_cert_as_username = cn + +## The TCP backlog defines the maximum length that the queue of pending +## connections can grow to. +## +## Value: Number >= 0 +listener.tcp.external.backlog = 1024 + +## The TCP send timeout for external MQTT connections. +## +## Value: Duration +listener.tcp.external.send_timeout = 15s + +## Close the TCP connection if send timeout. +## +## Value: on | off +listener.tcp.external.send_timeout_close = on + +## The TCP receive buffer(os kernel) for MQTT connections. +## +## See: http://erlang.org/doc/man/inet.html +## +## Value: Bytes +## listener.tcp.external.recbuf = 2KB + +## The TCP send buffer(os kernel) for MQTT connections. +## +## See: http://erlang.org/doc/man/inet.html +## +## Value: Bytes +## listener.tcp.external.sndbuf = 2KB + +## The size of the user-level software buffer used by the driver. +## Not to be confused with options sndbuf and recbuf, which correspond +## to the Kernel socket buffers. It is recommended to have val(buffer) +## >= max(val(sndbuf),val(recbuf)) to avoid performance issues because +## of unnecessary copying. val(buffer) is automatically set to the above +## maximum when values sndbuf or recbuf are set. +## +## See: http://erlang.org/doc/man/inet.html +## +## Value: Bytes +## listener.tcp.external.buffer = 2KB + +## Sets the 'buffer = max(sndbuf, recbuf)' if this option is enabled. +## +## Value: on | off +## listener.tcp.external.tune_buffer = off + +## The TCP_NODELAY flag for MQTT connections. Small amounts of data are +## sent immediately if the option is enabled. +## +## Value: true | false +listener.tcp.external.nodelay = true + +## The SO_REUSEADDR flag for TCP listener. +## +## Value: true | false +listener.tcp.external.reuseaddr = true + +##-------------------------------------------------------------------- +## Internal TCP Listener for MQTT Protocol + +## The IP address and port that the internal MQTT/TCP protocol listener +## will bind. +## +## Value: IP:Port, Port +## +## Examples: 11883, 127.0.0.1:11883, ::1:11883 +listener.tcp.internal = 127.0.0.1:11883 + +## The acceptor pool for internal MQTT/TCP listener. +## +## Value: Number +listener.tcp.internal.acceptors = 4 + +## Maximum number of concurrent MQTT/TCP connections. +## +## Value: Number +listener.tcp.internal.max_connections = 1024000 + +## Maximum internal connections per second. +## +## Value: Number +listener.tcp.internal.max_conn_rate = 1000 + +## Specify the {active, N} option for the internal MQTT/TCP Socket. +## +## Value: Number +listener.tcp.internal.active_n = 1000 + +## Zone of the internal MQTT/TCP listener belonged to. +## +## Value: String +listener.tcp.internal.zone = internal + +## Rate limit for the internal MQTT/TCP connections. +## +## See: listener.tcp.$name.rate_limit +## +## Value: limit,duration +## Default: 1MB incoming per second. +## listener.tcp.internal.rate_limit = 1MB,1s + +## The TCP backlog of internal MQTT/TCP Listener. +## +## See: listener.tcp.$name.backlog +## +## Value: Number >= 0 +listener.tcp.internal.backlog = 512 + +## The TCP send timeout for internal MQTT connections. +## +## See: listener.tcp.$name.send_timeout +## +## Value: Duration +listener.tcp.internal.send_timeout = 5s + +## Close the MQTT/TCP connection if send timeout. +## +## See: listener.tcp.$name.send_timeout_close +## +## Value: on | off +listener.tcp.internal.send_timeout_close = on + +## The TCP receive buffer(os kernel) for internal MQTT connections. +## +## See: listener.tcp.$name.recbuf +## +## Value: Bytes +listener.tcp.internal.recbuf = 64KB + +## The TCP send buffer(os kernel) for internal MQTT connections. +## +## See: http://erlang.org/doc/man/inet.html +## +## Value: Bytes +listener.tcp.internal.sndbuf = 64KB + +## The size of the user-level software buffer used by the driver. +## +## See: listener.tcp.$name.buffer +## +## Value: Bytes +## listener.tcp.internal.buffer = 16KB + +## Sets the 'buffer = max(sndbuf, recbuf)' if this option is enabled. +## +## See: listener.tcp.$name.tune_buffer +## +## Value: on | off +## listener.tcp.internal.tune_buffer = off + +## The TCP_NODELAY flag for internal MQTT connections. +## +## See: listener.tcp.$name.nodelay +## +## Value: true | false +listener.tcp.internal.nodelay = false + +## The SO_REUSEADDR flag for MQTT/TCP Listener. +## +## Value: true | false +listener.tcp.internal.reuseaddr = true + +##-------------------------------------------------------------------- +## MQTT/SSL - External SSL Listener for MQTT Protocol + +## listener.ssl.$name is the IP address and port that the MQTT/SSL +## listener will bind. +## +## Value: IP:Port | Port +## +## Examples: 8883, 127.0.0.1:8883, ::1:8883 +listener.ssl.external = 8883 + +## The acceptor pool for external MQTT/SSL listener. +## +## Value: Number +listener.ssl.external.acceptors = 16 + +## Maximum number of concurrent MQTT/SSL connections. +## +## Value: Number +listener.ssl.external.max_connections = 102400 + +## Maximum MQTT/SSL connections per second. +## +## Value: Number +listener.ssl.external.max_conn_rate = 500 + +## Specify the {active, N} option for the internal MQTT/SSL Socket. +## +## Value: Number +listener.ssl.external.active_n = 100 + +## Zone of the external MQTT/SSL listener belonged to. +## +## Value: String +listener.ssl.external.zone = external + +## The access control rules for the MQTT/SSL listener. +## +## See: listener.tcp.$name.access +## +## Value: ACL Rule +listener.ssl.external.access.1 = allow all + +## Rate limit for the external MQTT/SSL connections. +## +## Value: limit,duration +## Default: 100KB incoming per 10 seconds. +## listener.ssl.external.rate_limit = 100KB,10s + +## Enable the Proxy Protocol V1/2 if the EMQ cluster is deployed behind +## HAProxy or Nginx. +## +## See: listener.tcp.$name.proxy_protocol +## +## Value: on | off +## listener.ssl.external.proxy_protocol = on + +## Sets the timeout for proxy protocol. +## +## See: listener.tcp.$name.proxy_protocol_timeout +## +## Value: Duration +## listener.ssl.external.proxy_protocol_timeout = 3s + +## TLS versions only to protect from POODLE attack. +## +## See: http://erlang.org/doc/man/ssl.html +## +## Value: String, seperated by ',' +## listener.ssl.external.tls_versions = tlsv1.2,tlsv1.1,tlsv1 + +## TLS Handshake timeout. +## +## Value: Duration +listener.ssl.external.handshake_timeout = 15s + +## Path to the file containing the user's private PEM-encoded key. +## +## See: http://erlang.org/doc/man/ssl.html +## +## Value: File +listener.ssl.external.keyfile = etc/certs/key.pem + +## Path to a file containing the user certificate. +## +## See: http://erlang.org/doc/man/ssl.html +## +## Value: File +listener.ssl.external.certfile = etc/certs/cert.pem + +## Path to the file containing PEM-encoded CA certificates. The CA certificates +## are used during server authentication and when building the client certificate chain. +## +## Value: File +## listener.ssl.external.cacertfile = etc/certs/cacert.pem + +## The Ephemeral Diffie-Helman key exchange is a very effective way of +## ensuring Forward Secrecy by exchanging a set of keys that never hit +## the wire. Since the DH key is effectively signed by the private key, +## it needs to be at least as strong as the private key. In addition, +## the default DH groups that most of the OpenSSL installations have +## are only a handful (since they are distributed with the OpenSSL +## package that has been built for the operating system it’s running on) +## and hence predictable (not to mention, 1024 bits only). +## In order to escape this situation, first we need to generate a fresh, +## strong DH group, store it in a file and then use the option above, +## to force our SSL application to use the new DH group. Fortunately, +## OpenSSL provides us with a tool to do that. Simply run: +## openssl dhparam -out dh-params.pem 2048 +## +## Value: File +## listener.ssl.external.dhfile = etc/certs/dh-params.pem + +## A server only does x509-path validation in mode verify_peer, +## as it then sends a certificate request to the client (this +## message is not sent if the verify option is verify_none). +## You can then also want to specify option fail_if_no_peer_cert. +## More information at: http://erlang.org/doc/man/ssl.html +## +## Value: verify_peer | verify_none +## listener.ssl.external.verify = verify_peer + +## Used together with {verify, verify_peer} by an SSL server. If set to true, +## the server fails if the client does not have a certificate to send, that is, +## sends an empty certificate. +## +## Value: true | false +## listener.ssl.external.fail_if_no_peer_cert = true + +## This is the single most important configuration option of an Erlang SSL +## application. Ciphers (and their ordering) define the way the client and +## server encrypt information over the wire, from the initial Diffie-Helman +## key exchange, the session key encryption ## algorithm and the message +## digest algorithm. Selecting a good cipher suite is critical for the +## application’s data security, confidentiality and performance. +## +## The cipher list above offers: +## +## A good balance between compatibility with older browsers. +## It can get stricter for Machine-To-Machine scenarios. +## Perfect Forward Secrecy. +## No old/insecure encryption and HMAC algorithms +## +## Most of it was copied from Mozilla’s Server Side TLS article +## +## Value: Ciphers +listener.ssl.external.ciphers = ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA + +## Ciphers for TLS PSK. +## Note that 'listener.ssl.external.ciphers' and 'listener.ssl.external.psk_ciphers' cannot +## be configured at the same time. +## See 'https://tools.ietf.org/html/rfc4279#section-2'. +#listener.ssl.external.psk_ciphers = PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA + +## SSL parameter renegotiation is a feature that allows a client and a server +## to renegotiate the parameters of the SSL connection on the fly. +## RFC 5746 defines a more secure way of doing this. By enabling secure renegotiation, +## you drop support for the insecure renegotiation, prone to MitM attacks. +## +## Value: on | off +## listener.ssl.external.secure_renegotiate = off + +## A performance optimization setting, it allows clients to reuse +## pre-existing sessions, instead of initializing new ones. +## Read more about it here. +## +## See: http://erlang.org/doc/man/ssl.html +## +## Value: on | off +## listener.ssl.external.reuse_sessions = on + +## An important security setting, it forces the cipher to be set based +## on the server-specified order instead of the client-specified order, +## hence enforcing the (usually more properly configured) security +## ordering of the server administrator. +## +## Value: on | off +## listener.ssl.external.honor_cipher_order = on + +## Use the CN, DN or CRT field from the client certificate as a username. +## Notice that 'verify' should be set as 'verify_peer'. +## +## Value: cn | dn | crt +## listener.ssl.external.peer_cert_as_username = cn + +## TCP backlog for the SSL connection. +## +## See listener.tcp.$name.backlog +## +## Value: Number >= 0 +## listener.ssl.external.backlog = 1024 + +## The TCP send timeout for the SSL connection. +## +## See listener.tcp.$name.send_timeout +## +## Value: Duration +## listener.ssl.external.send_timeout = 15s + +## Close the SSL connection if send timeout. +## +## See: listener.tcp.$name.send_timeout_close +## +## Value: on | off +## listener.ssl.external.send_timeout_close = on + +## The TCP receive buffer(os kernel) for the SSL connections. +## +## See: listener.tcp.$name.recbuf +## +## Value: Bytes +## listener.ssl.external.recbuf = 4KB + +## The TCP send buffer(os kernel) for internal MQTT connections. +## +## See: listener.tcp.$name.sndbuf +## +## Value: Bytes +## listener.ssl.external.sndbuf = 4KB + +## The size of the user-level software buffer used by the driver. +## +## See: listener.tcp.$name.buffer +## +## Value: Bytes +## listener.ssl.external.buffer = 4KB + +## Sets the 'buffer = max(sndbuf, recbuf)' if this option is enabled. +## +## See: listener.tcp.$name.tune_buffer +## +## Value: on | off +## listener.ssl.external.tune_buffer = off + +## The TCP_NODELAY flag for SSL connections. +## +## See: listener.tcp.$name.nodelay +## +## Value: true | false +## listener.ssl.external.nodelay = true + +## The SO_REUSEADDR flag for MQTT/SSL Listener. +## +## Value: true | false +listener.ssl.external.reuseaddr = true + +##-------------------------------------------------------------------- +## External WebSocket listener for MQTT protocol + +## listener.ws.$name is the IP address and port that the MQTT/WebSocket +## listener will bind. +## +## Value: IP:Port | Port +## +## Examples: 8083, 127.0.0.1:8083, ::1:8083 +listener.ws.external = 8083 + +## The path of WebSocket MQTT endpoint +## +## Value: URL Path +listener.ws.external.mqtt_path = /mqtt + +## The acceptor pool for external MQTT/WebSocket listener. +## +## Value: Number +listener.ws.external.acceptors = 4 + +## Maximum number of concurrent MQTT/WebSocket connections. +## +## Value: Number +listener.ws.external.max_connections = 102400 + +## Maximum MQTT/WebSocket connections per second. +## +## Value: Number +listener.ws.external.max_conn_rate = 1000 + +## Simulate the {active, N} option for the MQTT/WebSocket connections. +## +## Value: Number +listener.ws.external.active_n = 100 + +## Rate limit for the MQTT/WebSocket connections. +## +## Value: Limit,Duration +## Default: 100KB incoming per 10 seconds. +## listener.ws.external.rate_limit = 100KB,10s + +## Zone of the external MQTT/WebSocket listener belonged to. +## +## Value: String +listener.ws.external.zone = external + +## The access control for the MQTT/WebSocket listener. +## +## See: listener.ws.$name.access +## +## Value: ACL Rule +listener.ws.external.access.1 = allow all + +## Verify if the protocol header is valid. Turn off for WeChat MiniApp. +## +## Value: on | off +listener.ws.external.verify_protocol_header = on + +## Use X-Forwarded-For header for real source IP if the EMQ X cluster is +## deployed behind NGINX or HAProxy. +## +## Value: String +## listener.ws.external.proxy_address_header = X-Forwarded-For + +## Use X-Forwarded-Port header for real source port if the EMQ X cluster is +## deployed behind NGINX or HAProxy. +## +## Value: String +## listener.ws.external.proxy_port_header = X-Forwarded-Port + +## Enable the Proxy Protocol V1/2 if the EMQ cluster is deployed behind +## HAProxy or Nginx. +## +## See: listener.ws.$name.proxy_protocol +## +## Value: on | off +## listener.ws.external.proxy_protocol = on + +## Sets the timeout for proxy protocol. +## +## See: listener.ws.$name.proxy_protocol_timeout +## +## Value: Duration +## listener.ws.external.proxy_protocol_timeout = 3s + +## The TCP backlog of external MQTT/WebSocket Listener. +## +## See: listener.ws.$name.backlog +## +## Value: Number >= 0 +listener.ws.external.backlog = 1024 + +## The TCP send timeout for external MQTT/WebSocket connections. +## +## See: listener.ws.$name.send_timeout +## +## Value: Duration +listener.ws.external.send_timeout = 15s + +## Close the MQTT/WebSocket connection if send timeout. +## +## See: listener.ws.$name.send_timeout_close +## +## Value: on | off +listener.ws.external.send_timeout_close = on + +## The TCP receive buffer(os kernel) for external MQTT/WebSocket connections. +## +## See: listener.ws.$name.recbuf +## +## Value: Bytes +## listener.ws.external.recbuf = 2KB + +## The TCP send buffer(os kernel) for external MQTT/WebSocket connections. +## +## See: listener.ws.$name.sndbuf +## +## Value: Bytes +## listener.ws.external.sndbuf = 2KB + +## The size of the user-level software buffer used by the driver. +## +## See: listener.ws.$name.buffer +## +## Value: Bytes +## listener.ws.external.buffer = 2KB + +## Sets the 'buffer = max(sndbuf, recbuf)' if this option is enabled. +## +## See: listener.ws.$name.tune_buffer +## +## Value: on | off +## listener.ws.external.tune_buffer = off + +## The TCP_NODELAY flag for external MQTT/WebSocket connections. +## +## See: listener.ws.$name.nodelay +## +## Value: true | false +listener.ws.external.nodelay = true + +## The compress flag for external MQTT/WebSocket connections. +## +## If this Value is set true,the websocket message would be compressed +## +## Value: true | false +## listener.ws.external.compress = true + +## The level of deflate options for external MQTT/WebSocket connections. +## +## See: listener.ws.$name.deflate_opts.level +## +## Value: none | default | best_compression | best_speed +## listener.ws.external.deflate_opts.level = default + +## The mem_level of deflate options for external MQTT/WebSocket connections. +## +## See: listener.ws.$name.deflate_opts.mem_level +## +## Valid range is 1-9 +## listener.ws.external.deflate_opts.mem_level = 8 + +## The strategy of deflate options for external MQTT/WebSocket connections. +## +## See: listener.ws.$name.deflate_opts.strategy +## +## Value: default | filtered | huffman_only | rle +## listener.ws.external.deflate_opts.strategy = default + +## The deflate option for external MQTT/WebSocket connections. +## +## See: listener.ws.$name.deflate_opts.server_context_takeover +## +## Value: takeover | no_takeover +## listener.ws.external.deflate_opts.server_context_takeover = takeover + +## The deflate option for external MQTT/WebSocket connections. +## +## See: listener.ws.$name.deflate_opts.client_context_takeover +## +## Value: takeover | no_takeover +## listener.ws.external.deflate_opts.client_context_takeover = takeover + +## The deflate options for external MQTT/WebSocket connections. +## +## See: listener.ws.$name.deflate_opts.server_max_window_bits +## +## Valid range is 8-15 +## listener.ws.external.deflate_opts.server_max_window_bits = 15 + +## The deflate options for external MQTT/WebSocket connections. +## +## See: listener.ws.$name.deflate_opts.client_max_window_bits +## +## Valid range is 8-15 +## listener.ws.external.deflate_opts.client_max_window_bits = 15 + +## The idle timeout for external MQTT/WebSocket connections. +## +## See: listener.ws.$name.idle_timeout +## +## Value: Duration +## listener.ws.external.idle_timeout = 60s + +## The max frame size for external MQTT/WebSocket connections. +## +## +## Value: Number +## listener.ws.external.max_frame_size = 0 + +##-------------------------------------------------------------------- +## External WebSocket/SSL listener for MQTT Protocol + +## listener.wss.$name is the IP address and port that the MQTT/WebSocket/SSL +## listener will bind. +## +## Value: IP:Port | Port +## +## Examples: 8084, 127.0.0.1:8084, ::1:8084 +listener.wss.external = 8084 + +## The path of WebSocket MQTT endpoint +## +## Value: URL Path +listener.wss.external.mqtt_path = /mqtt + +## The acceptor pool for external MQTT/WebSocket/SSL listener. +## +## Value: Number +listener.wss.external.acceptors = 4 + +## Maximum number of concurrent MQTT/Webwocket/SSL connections. +## +## Value: Number +listener.wss.external.max_connections = 16 + +## Maximum MQTT/WebSocket/SSL connections per second. +## +## See: listener.tcp.$name.max_conn_rate +## +## Value: Number +listener.wss.external.max_conn_rate = 1000 + +## Simulate the {active, N} option for the MQTT/WebSocket/SSL connections. +## +## Value: Number +listener.wss.external.active_n = 100 + +## Rate limit for the MQTT/WebSocket/SSL connections. +## +## Value: Limit,Duration +## Default: 100KB incoming per 10 seconds. +## listener.wss.external.rate_limit = 100KB,10s + +## Zone of the external MQTT/WebSocket/SSL listener belonged to. +## +## Value: String +listener.wss.external.zone = external + +## The access control rules for the MQTT/WebSocket/SSL listener. +## +## See: listener.tcp.$name.access. +## +## Value: ACL Rule +listener.wss.external.access.1 = allow all + +## See: listener.ws.external.verify_protocol_header +## +## Value: on | off +listener.wss.external.verify_protocol_header = on + +## See: listener.ws.external.proxy_address_header +## +## Value: String +## listener.wss.external.proxy_address_header = X-Forwarded-For + +## See: listener.ws.external.proxy_port_header +## +## Value: String +## listener.wss.external.proxy_port_header = X-Forwarded-Port + +## Enable the Proxy Protocol V1/2 support. +## +## See: listener.tcp.$name.proxy_protocol +## +## Value: on | off +## listener.wss.external.proxy_protocol = on + +## Sets the timeout for proxy protocol. +## +## See: listener.tcp.$name.proxy_protocol_timeout +## +## Value: Duration +## listener.wss.external.proxy_protocol_timeout = 3s + +## TLS versions only to protect from POODLE attack. +## +## See: listener.ssl.$name.tls_versions +## +## Value: String, seperated by ',' +## listener.wss.external.tls_versions = tlsv1.2,tlsv1.1,tlsv1 + +## Path to the file containing the user's private PEM-encoded key. +## +## See: listener.ssl.$name.keyfile +## +## Value: File +listener.wss.external.keyfile = etc/certs/key.pem + +## Path to a file containing the user certificate. +## +## See: listener.ssl.$name.certfile +## +## Value: File +listener.wss.external.certfile = etc/certs/cert.pem + +## Path to the file containing PEM-encoded CA certificates. +## +## See: listener.ssl.$name.cacert +## +## Value: File +## listener.wss.external.cacertfile = etc/certs/cacert.pem + +## See: listener.ssl.$name.dhfile +## +## Value: File +## listener.ssl.external.dhfile = etc/certs/dh-params.pem + +## See: listener.ssl.$name.vefify +## +## Value: vefify_peer | verify_none +## listener.wss.external.verify = verify_peer + +## See: listener.ssl.$name.fail_if_no_peer_cert +## +## Value: false | true +## listener.wss.external.fail_if_no_peer_cert = true + +## See: listener.ssl.$name.ciphers +## +## Value: Ciphers +listener.wss.external.ciphers = ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA + +## Ciphers for TLS PSK. +## Note that 'listener.wss.external.ciphers' and 'listener.wss.external.psk_ciphers' cannot +## be configured at the same time. +## See 'https://tools.ietf.org/html/rfc4279#section-2'. +## listener.wss.external.psk_ciphers = PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA + +## See: listener.ssl.$name.secure_renegotiate +## +## Value: on | off +## listener.wss.external.secure_renegotiate = off + +## See: listener.ssl.$name.reuse_sessions +## +## Value: on | off +## listener.wss.external.reuse_sessions = on + +## See: listener.ssl.$name.honor_cipher_order +## +## Value: on | off +## listener.wss.external.honor_cipher_order = on + +## See: listener.ssl.$name.peer_cert_as_username +## +## Value: cn | dn | crt +## listener.wss.external.peer_cert_as_username = cn + +## TCP backlog for the WebSocket/SSL connection. +## +## See: listener.tcp.$name.backlog +## +## Value: Number >= 0 +listener.wss.external.backlog = 1024 + +## The TCP send timeout for the WebSocket/SSL connection. +## +## See: listener.tcp.$name.send_timeout +## +## Value: Duration +listener.wss.external.send_timeout = 15s + +## Close the WebSocket/SSL connection if send timeout. +## +## See: listener.tcp.$name.send_timeout_close +## +## Value: on | off +listener.wss.external.send_timeout_close = on + +## The TCP receive buffer(os kernel) for the WebSocket/SSL connections. +## +## See: listener.tcp.$name.recbuf +## +## Value: Bytes +## listener.wss.external.recbuf = 4KB + +## The TCP send buffer(os kernel) for the WebSocket/SSL connections. +## +## See: listener.tcp.$name.sndbuf +## +## Value: Bytes +## listener.wss.external.sndbuf = 4KB + +## The size of the user-level software buffer used by the driver. +## +## See: listener.tcp.$name.buffer +## +## Value: Bytes +## listener.wss.external.buffer = 4KB + +## The TCP_NODELAY flag for WebSocket/SSL connections. +## +## See: listener.tcp.$name.nodelay +## +## Value: true | false +## listener.wss.external.nodelay = true + +## The compress flag for external WebSocket/SSL connections. +## +## If this Value is set true,the websocket message would be compressed +## +## Value: true | false +## listener.wss.external.compress = true + +## The level of deflate options for external WebSocket/SSL connections. +## +## See: listener.wss.$name.deflate_opts.level +## +## Value: none | default | best_compression | best_speed +## listener.wss.external.deflate_opts.level = default + +## The mem_level of deflate options for external WebSocket/SSL connections. +## +## See: listener.wss.$name.deflate_opts.mem_level +## +## Valid range is 1-9 +## listener.wss.external.deflate_opts.mem_level = 8 + +## The strategy of deflate options for external WebSocket/SSL connections. +## +## See: listener.wss.$name.deflate_opts.strategy +## +## Value: default | filtered | huffman_only | rle +## listener.wss.external.deflate_opts.strategy = default + +## The deflate option for external WebSocket/SSL connections. +## +## See: listener.wss.$name.deflate_opts.server_context_takeover +## +## Value: takeover | no_takeover +## listener.wss.external.deflate_opts.server_context_takeover = takeover + +## The deflate option for external WebSocket/SSL connections. +## +## See: listener.wss.$name.deflate_opts.client_context_takeover +## +## Value: takeover | no_takeover +## listener.wss.external.deflate_opts.client_context_takeover = takeover + +## The deflate options for external WebSocket/SSL connections. +## +## See: listener.wss.$name.deflate_opts.server_max_window_bits +## +## Valid range is 8-15 +## listener.wss.external.deflate_opts.server_max_window_bits = 15 + +## The deflate options for external WebSocket/SSL connections. +## +## See: listener.wss.$name.deflate_opts.client_max_window_bits +## +## Valid range is 8-15 +## listener.wss.external.deflate_opts.client_max_window_bits = 15 + +## The idle timeout for external WebSocket/SSL connections. +## +## See: listener.wss.$name.idle_timeout +## +## Value: Duration +## listener.wss.external.idle_timeout = 60s + +## The max frame size for external WebSocket/SSL connections. +## +## Value: Number +## listener.wss.external.max_frame_size = 0 + +##-------------------------------------------------------------------- +## Modules +##-------------------------------------------------------------------- + +##-------------------------------------------------------------------- +## Presence Module + +## Enable Presence Module. +## +## Value: on | off +module.presence = on + +## Sets the QoS for presence MQTT message. +## +## Value: 0 | 1 | 2 +module.presence.qos = 1 + +##-------------------------------------------------------------------- +## Subscription Module + +## Enable Subscription Module. +## +## Value: on | off +module.subscription = off + +## Subscribe the Topics automatically when client connected. +## module.subscription.1.topic = $client/%c +## Qos of the subscription: 0 | 1 | 2 +## module.subscription.1.qos = 1 + +## module.subscription.2.topic = $user/%u +## module.subscription.2.qos = 1 + +##-------------------------------------------------------------------- +## Rewrite Module + +## Enable Rewrite Module. +## +## Value: on | off +module.rewrite = off + +## {rewrite, Topic, Re, Dest} +## module.rewrite.rule.1 = x/# ^x/y/(.+)$ z/y/$1 +## module.rewrite.rule.2 = y/+/z/# ^y/(.+)/z/(.+)$ y/z/$2 + +##------------------------------------------------------------------- +## Plugins +##------------------------------------------------------------------- + +## The etc dir for plugins' config. +## +## Value: Folder +plugins.etc_dir = etc/plugins/ + +## The file to store loaded plugin names. +## +## Value: File +plugins.loaded_file = data/loaded_plugins + +## File to store loaded plugin names. +plugins.expand_plugins_dir = plugins/ + +##-------------------------------------------------------------------- +## Broker +##-------------------------------------------------------------------- + +## System interval of publishing $SYS messages. +## +## Value: Duration +## Default: 1m, 1 minute +broker.sys_interval = 1m + +## System heartbeat interval of publishing following heart beat message: +## - "$SYS/brokers//uptime" +## - "$SYS/brokers//datetime" +## +## Value: Duration +## Default: 30s +broker.sys_heartbeat = 30s + +## Enable global session registry. +## +## Value: on | off +broker.enable_session_registry = on + +## Session locking strategy in a cluster. +## +## Value: Enum +## - local +## - one +## - quorum +## - all +broker.session_locking_strategy = quorum + +## Dispatch strategy for shared subscription +## +## Value: Enum +## - random +## - round_robin +## - sticky +## - hash +broker.shared_subscription_strategy = random + +## Enable/disable shared dispatch acknowledgement for QoS1 and QoS2 messages +## This should allow messages to be dispatched to a different subscriber in +## the group in case the picked (based on shared_subscription_strategy) one # is offline +## +## Value: Enum +## - true +## - false +broker.shared_dispatch_ack_enabled = false + +## Enable batch clean for deleted routes. +## +## Value: Flag +broker.route_batch_clean = off + +##-------------------------------------------------------------------- +## System Monitor +##-------------------------------------------------------------------- + +## Enable Long GC monitoring. Disable if the value is 0. +## Notice: don't enable the monitor in production for: +## https://github.com/erlang/otp/blob/feb45017da36be78d4c5784d758ede619fa7bfd3/erts/emulator/beam/erl_gc.c#L421 +## +## Value: Duration +## - h: hour +## - m: minute +## - s: second +## - ms: milliseconds +## +## Examples: +## - 2h: 2 hours +## - 30m: 30 minutes +## - 0.1s: 0.1 seconds +## - 100ms : 100 milliseconds +## +## Default: 0ms +sysmon.long_gc = 0 + +## Enable Long Schedule(ms) monitoring. +## +## See: http://erlang.org/doc/man/erlang.html#system_monitor-2 +## +## Value: Duration +## - h: hour +## - m: minute +## - s: second +## - ms: milliseconds +## +## Examples: +## - 2h: 2 hours +## - 30m: 30 minutes +## - 0.1s: 0.1 seconds +## - 100ms: 100 milliseconds +## +## Default: 0ms +sysmon.long_schedule = 240ms + +## Enable Large Heap monitoring. +## +## See: http://erlang.org/doc/man/erlang.html#system_monitor-2 +## +## Value: bytes +## +## Default: 8M words. 32MB on 32-bit VM, 64MB on 64-bit VM. +sysmon.large_heap = 8MB + +## Enable Busy Port monitoring. +## +## See: http://erlang.org/doc/man/erlang.html#system_monitor-2 +## +## Value: true | false +sysmon.busy_port = false + +## Enable Busy Dist Port monitoring. +## +## See: http://erlang.org/doc/man/erlang.html#system_monitor-2 +## +## Value: true | false +sysmon.busy_dist_port = true + +## The time interval for the periodic cpu check +## +## Value: Duration +## -h: hour, e.g. '2h' for 2 hours +## -m: minute, e.g. '5m' for 5 minutes +## -s: second, e.g. '30s' for 30 seconds +## +## Default: 60s +os_mon.cpu_check_interval = 60s + +## The threshold, as percentage of system cpu, for how much system cpu can be used before the corresponding alarm is set. +## +## Default: 80% +os_mon.cpu_high_watermark = 80% + +## The threshold, as percentage of system cpu, for how much system cpu can be used before the corresponding alarm is clear. +## +## Default: 60% +os_mon.cpu_low_watermark = 60% + +## The time interval for the periodic memory check +## +## Value: Duration +## -h: hour, e.g. '2h' for 2 hours +## -m: minute, e.g. '5m' for 5 minutes +## -s: second, e.g. '30s' for 30 seconds +## +## Default: 60s +os_mon.mem_check_interval = 60s + +## The threshold, as percentage of system memory, for how much system memory can be allocated before the corresponding alarm is set. +## +## Default: 70% +os_mon.sysmem_high_watermark = 70% + +## The threshold, as percentage of system memory, for how much system memory can be allocated by one Erlang process before the corresponding alarm is set. +## +## Default: 5% +os_mon.procmem_high_watermark = 5% + +## The time interval for the periodic process limit check +## +## Value: Duration +## +## Default: 30s +vm_mon.check_interval = 30s + +## The threshold, as percentage of processes, for how many processes can simultaneously exist at the local node before the corresponding alarm is set. +## +## Default: 80% +vm_mon.process_high_watermark = 80% + +## The threshold, as percentage of processes, for how many processes can simultaneously exist at the local node before the corresponding alarm is clear. +## +## Default: 60% +vm_mon.process_low_watermark = 60% + + diff --git a/docker/emqx4.0/emqx_auth_http.conf b/docker/emqx4.0/emqx_auth_http.conf new file mode 100644 index 00000000..0366654d --- /dev/null +++ b/docker/emqx4.0/emqx_auth_http.conf @@ -0,0 +1,133 @@ +##-------------------------------------------------------------------- +## HTTP Auth/ACL Plugin +##-------------------------------------------------------------------- + + +##------------------------------------------------------------------------------ +## SSL options + +## Path to the file containing PEM-encoded CA certificates. The CA certificates +## are used during server authentication and when building the client certificate chain. +## +## Value: File +## auth.http.ssl.cacertfile = etc/certs/ca.pem + +## The path to a file containing the client's certificate. +## +## Value: File +## auth.http.ssl.certfile = etc/certs/client-cert.pem + +## Path to a file containing the client's private PEM-encoded key. +## +## Value: File +## auth.http.ssl.keyfile = etc/certs/client-key.pem + +##-------------------------------------------------------------------- +## HTTP Request Headers +## +## Example: auth.http.header.Accept-Encoding = * +## +## Value: String +## auth.http.header.Accept = */* + +##-------------------------------------------------------------------- +## Authentication request. +## +## Variables: +## - %u: username +## - %c: clientid +## - %a: ipaddress +## - %r: protocol +## - %P: password +## - %p: sockport of server accepted +## - %C: common name of client TLS cert +## - %d: subject of client TLS cert +## +## Value: URL +auth.http.auth_req = http://localhost:8080/iot/tool/mqtt/auth +## Value: post | get | put +auth.http.auth_req.method = post +## Value: Params +auth.http.auth_req.params = clientid=%c,username=%u,password=%P + +##-------------------------------------------------------------------- +## Superuser request. +## +## Variables: +## - %u: username +## - %c: clientid +## - %a: ipaddress +## - %r: protocol +## - %P: password +## - %p: sockport of server accepted +## - %C: common name of client TLS cert +## - %d: subject of client TLS cert +## +## Value: URL +# auth.http.super_req = http://127.0.0.1:8991/mqtt/superuser +## Value: post | get | put +# auth.http.super_req.method = post +## Value: Params +# auth.http.super_req.params = clientid=%c,username=%u + +##-------------------------------------------------------------------- +## ACL request. +## +## Variables: +## - %A: 1 | 2, 1 = sub, 2 = pub +## - %u: username +## - %c: clientid +## - %a: ipaddress +## - %r: protocol +## - %m: mountpoint +## - %t: topic +## +## Value: URL +# auth.http.acl_req = http://127.0.0.1:8991/mqtt/acl +## Value: post | get | put +# auth.http.acl_req.method = get +## Value: Params +# auth.http.acl_req.params = access=%A,username=%u,clientid=%c,ipaddr=%a,topic=%t,mountpoint=%m + +##------------------------------------------------------------------------------ +## Http Reqeust options + +## Time-out time for the http request, 0 is never timeout. +## +## Value: Duration +## -h: hour, e.g. '2h' for 2 hours +## -m: minute, e.g. '5m' for 5 minutes +## -s: second, e.g. '30s' for 30 seconds +## +## Default: 0 +## auth.http.request.timeout = 0 + +## Connection time-out time, used during the initial request +## when the client is connecting to the server +## +## Value: Duration +## +## Default is same with the timeout option +## auth.http.request.connect_timout = 0 + +## Re-send http reuqest times +## +## Value: integer +## +## Default: 3 +auth.http.request.retry_times = 3 + +## The interval for re-sending the http request +## +## Value: Duration +## +## Default: 1s +auth.http.request.retry_interval = 1s + +## The 'Exponential Backoff' mechanism for re-sending request. The actually +## re-send time interval is `interval * backoff ^ times` +## +## Value: float +## +## Default: 2.0 +auth.http.request.retry_backoff = 2.0 diff --git a/docker/emqx4.0/emqx_web_hook.conf b/docker/emqx4.0/emqx_web_hook.conf new file mode 100644 index 00000000..5fe157dc --- /dev/null +++ b/docker/emqx4.0/emqx_web_hook.conf @@ -0,0 +1,22 @@ + +web.hook.api.url = http://localhost:8080/iot/tool/mqtt/webhook + +## Encode message payload field +## +## Value: base64 | base62 +## +## Default: undefined +## web.hook.encode_payload = base64 + +# web.hook.rule.client.connect.1 = {"action": "on_client_connect"} +# web.hook.rule.client.connack.1 = {"action": "on_client_connack"} +web.hook.rule.client.connected.1 = {"action": "on_client_connected"} +web.hook.rule.client.disconnected.1 = {"action": "on_client_disconnected"} +# web.hook.rule.client.subscribe.1 = {"action": "on_client_subscribe"} +# web.hook.rule.client.unsubscribe.1 = {"action": "on_client_unsubscribe"} +# web.hook.rule.session.subscribed.1 = {"action": "on_session_subscribed"} +# web.hook.rule.session.unsubscribed.1 = {"action": "on_session_unsubscribed"} +# web.hook.rule.session.terminated.1 = {"action": "on_session_terminated"} +# web.hook.rule.message.publish.1 = {"action": "on_message_publish"} +# web.hook.rule.message.delivered.1 = {"action": "on_message_delivered"} +# web.hook.rule.message.acked.1 = {"action": "on_message_acked"} diff --git a/docker/nginx.conf b/docker/nginx.conf new file mode 100644 index 00000000..9e44a220 --- /dev/null +++ b/docker/nginx.conf @@ -0,0 +1,47 @@ +worker_processes 1; + +events { + worker_connections 1024; +} + +http { + include mime.types; + default_type application/octet-stream; + sendfile on; + keepalive_timeout 65; + + gzip on; + gzip_min_length 1k; + gzip_buffers 16 64K; + gzip_http_version 1.1; + gzip_comp_level 5; + gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml; + gzip_vary on; + gzip_proxied expired no-cache no-store private auth; + gzip_disable "MSIE [1-6]\."; + + server { + listen 80; + server_name localhost; + charset utf-8; + + location / { + root /var/data/wumei/vue; + try_files $uri $uri/ /index.html; + index index.html index.htm; + } + + location /prod-api/ { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header REMOTE-HOST $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://localhost:8080/; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root html; + } + } +} \ No newline at end of file diff --git a/document/message.png b/document/message.png new file mode 100644 index 0000000000000000000000000000000000000000..5873ec9e1cd34fe011c9f63302120a0a25c719a7 GIT binary patch literal 42991 zcmdSBi$Bxv8$Ui4Q;x~0a_p!aD^bo$ioeO@bY=R)(OLi`^DHhms1}UiS>?q5Z1OewOhf zckD=Tjzq(qQcP9K@x1x}&5Kr{cW}aQQg$wl! zGT>J^Pi1}Y$A{?578+q+-|#B6{gC%Mtf_2)Fl77Y^-mmLBW!u&yCutny$1-y{K4wq zuaMLN?SC(d;@(7!LtWGYxvPBN6|a8=QLjywBLH?yu9*Uxx3=Wo0$%n&T+7GbAB{R;*ZnXP=|NF!;GsD z>waYLmklFVZ2jUI(a3GHdsCZ!hve-ramAL`9tMY0lpB8;zA^@=9$ z$=KBaT~zLpH@CnMcR48Set0dTNIw(2J4G^$>wucq?~y6?5;l9ne53r$rJc(Z`=T-` zoNH(C*E4bZ_bXC(Ado)!jtBh)nNb!N((7q%VlhE`boR5H?wLVPcdX{`;KYmbT_iuCQz+7FfV%3bu zD#r(6PP>5YCFT+yD|&k{bB0uW{4P^peSKt~p$NBU+#wFIf;f0}Tx7R>Oa7?5VErM* zFk_d#GLOVv4&3`75L}$qfta&=jc|p_D)qmnx}YX&P4M;IQ*e^!Scwg{?c3giIZuOQ zh+ro;qg(B`gD&a@P3F>778$P1X~Om5Y5FHIwd;q0$|c|m5Cjsn3%n{YKNJX^g5Q(pH~#VJB2ZAq=_}CdXKfXq z3FqU%3i(9t1AJkaiw5xpZxDWujUW)!qxHWIS)0jzoVH9y<(X(#3r)3Dm8kWNC7UQe z^{)s$ReTz({W*rnO=(U^dEH;6)i0e28~Ai$&x>V6*8>vZBP7$fvx0lT56V2IJMg_r z65?`5@D?Fc3+HN%%!^jWh~ZRpB@p{N#!}?Vk-|*Hlf@~@9hjG%)h&%kCN4Qe^W%ef zpuuBVjy>Mc({r=oi&^bAr$>W~`KRE|ZoUP}c&cZ`HroO}8W zKka)0jfZ|KGM5D&hU_{g>%eQ(TmD{+U84y-6}QsD(6(oJ1a?#do>zkhhCWUhe?CC?OQKV zY~z><3*6tHK05RqJy__I3j2sVCyR?V%S7 z{S&xX;!|)@dzrwtyMfM*EYT$yJ8_7S_{^nvS~cWasitv84Khx==k_U{PP9m zSby`EgQ|NCf*%rrN{(Ep6gcM)owBM*ch*jYopO^Bx~|lv3xUY)@6wcgVVi+c)$G7X zR0{BvNV~}ZCk|r&IIK#U=h6kFGh%2=Foj1hvNjL|9?SN(P=kk^XOm02&|#{49Z)Z3 z1Ul*>&xxLw*R}+OfNF=q@n!_+e@P*(EhbOt!6mq-;LYHdF7bM?mp%)YzFP&-g&OOG z!HrZ{OV<-U)k86S0_>-K=SqWcX6E?ly>c(K?U62_sjv!^mh~;!yE$)f{l`+}fJD8V zZAAE*U*stf%iYAx%M0EmYohhRx6hGlt1`#H5+bPk^Zbu^kh8wiFA55YlRn%!sVt9r2`VCnP0%506%X^KXo8kPGsN1yF3R<<%iz6&hT6PmxTHiTa&bDPkK{4o;d^&G!_k13vZ zke1Og-M!UvKTOiMp}<`FkHv0qY6n(;ky8a?9?9^3JF73K9O;+YkXr!XdAGkPWoT^? zUn%a0K7qazV0C0R*f;!irX-_tYpWp{E-yGu{o}U6ZQzi^J$7wHy6K)zt(@MYmFa9| z^~WQT3{5xfLU?SHt4Tt|sw_cegpxk{v=~Iff@PEsMvLcAq?*JaPCNKlcY6aV*?{Xo z$lHS1^TgAKYtaiADk~3PN~GO-p{>b){aA0%?z@O)MOvAgu6D_p*$*Zn7W z--lj;PPKT9zXtH&YEGFMmH&aUe~+yUgX9Af-GdJLIulF^7WtS8Gi-xPHMSqdd@SMp zyIk=L3gp$o<$UcvP3{AMf>$EqqhFc-uHKl?sa~Be+-eE#>w z?+D!5n5U+_cdA2SP32wzXNzufSsb7yQIE&3@O~^>v}A-v`F2832&zK7E~6&)qA_tI z?xe8QRSy)#!cA(|_#Xc8Qycc{wQ`g@&n9MtU$HmhymA{oHW0V|L7OMtT|_6cmaHr8 zaL)q6(A+51Hm~;Wbpo{L)xN0~zx^+@&8-etDG9tjlFFN9V;=mttv)UHK77ZoEw*Lb zPI`UgbYi^5m!Am72{<8?bb?Y%nN$F_xXAD&)U5@Z^nwJpV-Aw4>Rt*iPNeuA@Rj+6 z+cN0B)_7@A{$Vo=I!*B8nZ~pGJ3`xdPV^Ql(<7M6`8-o_nKK?Kh%+UKVhd@HT&Ti` z&z!{Z995c=iYB?`TZs@QSY0y@C5`$5lEOid)aENZNn#;=7vlGs@|#a^gzlS)-V+6f zqH`m05A+S;MDOT#J`Xy$s3-ZwDg_=U|J(Z)P7+jm)~gd?U;^UQXJ zUWCaDau;gP-cfs%*oL79`KC}2#i69I`ZI_`p{s5M6@+M>gy--NLru z>0TO42lb_@6Pha~G%(C@pep%U=&1{jWk@|qeS^VR)xN>^^WO$nqt1e;@`~K)>m2~o zd0`vhfq5sC&pZfdpg51MS98N+K?g^JgJijOAHsiPKYy5_ua;7U2Z%`G4&0(s@`BqL zp6M{kSYwic@T|x#{-1~mgmkDZx>+i)?h<0(EUI69WzDh$N1CwVT|wkS-Zuh;c$2gK zDFp1dO+nC#@0Bc10$*71p-)^7%7TU7R2b)T@4P=r5>4X_V~S0+gpS|i#cb4Q-)bC^ z0hY3Id_AkIJi1a`t5%?KNyOKVy7B?hMe-5eom>iMO?NjaM@fijJmG~b6_W4;EtHpx zz-&V#fa3Rl@rN0I0CDn4l;3=`RA@VPHlcdJlb`YgN_LYemUl$rIAu+D{%A_Il9VHL zfOJ!U-sqx_TfAtRVk4N@xeF8UNFpR8BRcX2VySrJ+b@Wgqx`@(*Mn!F2G%a`Q+MS;`>)RM@S3QuMW>PrriT{6!FK1?rfuc5*NN2W%f;K1`nO#eoea z6eqBt?+((T3sSRI4?-I*8yUhQiemMPWo`jzJT->-7ye(C{Gc?0EP-2X&dV~dDvK7y z_NdCI!aQeYoqBplFN@6iB5@{Pkx*<|`@zPz=y&P$olxI{sBbd9)*j}6mcw(YnlrpNnA_$Is92YyS{UlRb}w@p*g%13AT zBkJ@1SO%Rq*l4XJsODS+os*>;%iEA$DcSYxk#&{`c&7@l*e?}equ;MVC4I*(a36o` zD`d$bRK2yblrVBukh_0-w`P*^xQEQZB`EV&+EV-#j?!>vc7F4cw##gK#`aTZdN2r# z8==@nFXtu18<8}aW|G*!?_;-Cu0)59d6x$Yd8}M&=st4BxON+mi_rcsz+;^c0p>9; z5Mhk8+7qe<8N@lo!Y%4l6}yeiGlhDMe|JC|Ah&sVkC`6Nu10+QrzhEK5fi!BC*bd2 zL4H^BpExPlNV>DqOcbcXsE+v@+FaSF4?2KxjLX9)nO{;8To1BOnXUF4AzZ8sV;33x z>OS}18C2z25arW6E7u2c81eeKiWop-?6~K{Sj$8(jYA zQLKr~!&h(LpOMLuzO}G5E0PE=>VGvAU7S3{k?y$*Ahg_oXY z&uN~`U;NI!G?`1#N2d7bhWyCiYIt;-p)9yv>ruenyZv29Udt5@_K54BuCtT>n5xCgdKd4E1LMmK=52rT^1*VRX$N zDF04e-OlYtn->4!CPhRA9Fb!P>$58-L{edeac#nH=0cFN^~dlUJb*LEbWeluxvwZX z2_UqXCt_kIZy&DeV^hO0vIbv%yWoK#A4@Xax4T3|CX=Ya`r0a+3Pah`qEar$YzP1% z&?Pr0GbIqiBlmGCu12W~{aTf0D*C6+*mo|2&%NJ@wgg3iHmWjV&+DOb2F4q+9Y=F2 z%%!HH*O#6q#(b9&^~fbT%=gTog;kj&Y_f8#W|RA3OiD|CmsIx{JSY{$QLHR<+BUG`!A}9O zw?q;kQFYM62F6IW0NBTTZ638hE8Lf2QdY0J4h78;t?o8HgUw@P1;Vh@&ObiYi;cKJpZ(T+A6 zHJU+QULGjRMCpI=0}JY8QJ?=?NubzQ|1d3+0J@#_Dj-S_3v}xPJc3qBvZ%muX|JItr+HhJWgN5jJod|WlZEJ8W&pL0dHjqnyZ!)ZHWYkoj12S~ zcsQu}4l9v9^`yZFdwW!aLf7zFlpSH_#@?2lPJLbfY)TL#rBT~Y6_hbEuHG^h02 zUma*bXOMB0N(L)?-PBc~9Di3gMzYJbJH=duIPKpa<}X_Q)9^SHbC(z|x+Q2*2@XhQ zgthn6)}{(Nl^T7T60YH60GLK)t`4rH$%hv@rwXazx{xu2Yn2@lyFV6W)YyQPzVMzF zN8&_?FAoexzg0CJ9JA+& zez`_!FL|n}0B>Ji7PL5@Ka;;7GJLataD0nM3et@68F&KC*M{_Qu?yGB)I*>a;wJS3 z)IGZ%a52Xorw}eJM0*dXvZAi`KAe&H%3OTV6vS$GUK4Nn)37)$u09=}|Ad-KPI$)Ua!H1u?$ zPpX1?<4HHSofZ)})MHcYj0sH`CMS6C{4z^%VrT3p_E)%UAC6?&G&o2}<9`8Y0jECc zo*%+pZgwbNlE-VG{H7YW@B2?eh=ZGn)PeUGfV*kP2ah>zeCHB95+>Mq2co}6%`p3o zN(x~nWg)o(eaIZvfq7kJoTKsT&D-?PB+<0$TE+i!jv`Pq_RAqy0 zku>+W0p=>;Gpc2WclMld$mT~I^!)&?Id1m_xUd(ASnff==13G(Hk-A za<()2UTB;nz+TP_=(n1yTNO_VLzx@ml}!y3oy#m^??~DVI*(l8iXd3CMEJ+f%0Bn+ z#DtQeq8tah;;sW^UTwXo2qBBdkb-P4+#0~mBE4=J$n!SRJ8ySl{N^t_BAdousC6g~ z{zQY$>MGT%uQh)onyrh9_9On>eYPQJJld)Wdj_hnilypQ{tBKsTiaWbx18*@9BAlc z{%VlL!~BtPC_V3d=Y$6t++&*={;l<}Xr%+|67_nzALO*l&E-GQU}4@;ACEa&$`MAz z#7@yADB)*jBO&B9P&f_QA`12&&&@hkYSQZYt}LK5$N^~>i_zt}F|Mi<=15sP_QI>* z``}gXsTO%Ll}(skOyJS_izTK%JnNnf=8ZGWj`k$`I;B$kRSpwusaEygxf-*FUM%&eiK-(yn)*r9r?B$-tQp}MC!as21S52eC5QJ3bM;zj zaA;8uBrmN7H~4=AL450yK~LV5ClyY{lW$h#;t?4lcD^wM^k>(H6xt#zF(M}|45b8$48s`RN3x95rN-Q?$$ z93~-J*WOth_6_|BP)+eSErAe*(fai%N#d}dEec72toA!=d6sAuyY1f}wxr48 z>99?_g!%#V^fXS__fQn7w4zyH;_SYbJv9o?s}r!8ymX~QBHf4jnssx8^AU>-Ou^F8 zNXki}#o(1S?mi`K<1cK;R;G%{m6PwebaUMcthnjV7KJpoud}t}sIQwiT1Mjq6yP^m)G&FD)H>=ux=4c~5 z_4mk!rs^MS{6)<;1|)EB+g;|1ZtbsfG8U~;KW}g)a1Yc=Hl_NZrD&EB()@T?tN!qF zX)##K7R_m%nJnLRci26||7=Vwznb!7M# z6S&95&nrYbnhYJ!NgC@%ofNaW{ryz);L3#MQ7Z$;?8|3Am4~Nh@b|9gI+n89{r4Mv zdp^4^6OWn0X=lU~nJ`+wXI)$fTyH$bqfV$gLH}dO8e{f;oi^)bSp|;<2u_E~-FT}4 zx%v|$xElVhWB9z`<^5coo)_c=UIIV7e)H{b7$WAXmM!)IFKWVp4a|a@SIg^I&no(l z4-fh@o*Lzae8X`tBI!#S*_SgjGrw+9!l~Zn_|1+y)FzQgS|7df=H`tasgRLV4Ni7C zo8R^kog8<}mRU01l*+LXGU=?UFQzxKc2-I5*^{5sw*m)|TOlW2#)87GIwC9kz%u_B zkEf>XsD6=H8$6#{#(YoLdwb44Oi=%%mE_|4c4_K#s-G# zFxmgp-hkp?uY}$u>&RXP{>OY3`)z`+Fy0CnezGI zuZ4_Y5HSi%r&{JhRQy?bLNg|VJ)M-WnNlK3tyGf`55JP``SwAq$mKmQE}yE*rPX2w zB)(J?42awsb(RO%sqS0Ro6V-}-;0T(jhR#~$Uy!8EKoCA>II0TvrXgbQf6laTHmKD z*{N$Sz|Tk-v@%u@EB>Tk*&0$aOlC50m*VO0#VSw`4k1pnnGMxb-1Ls@fbqF&(G#*t zU@sGD+~4k=yXK;JFb{o%UYQB-Qk>gbKQBu+{8wB18Xn@T*`vMI6#|>fdW?(4b8i_E z-LWVN*%2BM?sO|#R$0*Mdd!AsV0H(Gf(V!GK{zRac`&K}fT~exNQYNe|!n> zc~1U6Vv4_EF)@65D)|poF7}HO(e|~)bUnT7az|Qsgdpga^KUOjp)xo$r&>$_8r7li z{9sKmb(0GfR-KQdu6LF2-Tgi*5?hV!8PG_Dov^`YS?>Sh*T20#l{>MXo=LC3_t0O^ z_2AiNj;p=IX@uESIEOytrH!YvRD{)u(fK-8Zd}G+s@!sOoDe(Ridk%WsD|TJ*Qn9j)rPCsr;+4F<9L+~!6iyB)C|OD2 zNVjq2Q!U@(%1hO<7t2-1w4x4v-!Vu4a7-) zN|-|Da)S!%f(B~oGqs)-@FZaTp~9*T1Q%Jz*M~UO*FGQySY|z)uG!Nx@;f&|(8}s35M-V5rN1Dg zcz>MPJIGaoXH(JDAJXrQgj4bEDuWWtx8)xa%XZ0~NP``88csz`O!U?_VG+zVK$XkF z*0&>6o_K*~u;rn3#e6mAQ=gXewLP*+?at@s3wnBa$|_iImwUGB$yX*GTuZE5h^5nqw|Jz)f$=wC+A9L>)bDD%@@ z3O8loiD?85XZzrHKCsPrZhD?Y2ekX#yY^8KsoZKRlgo($SN>GueGg2v?A2g2Go&PE z{*c6eh2t(C@H>4lKet~I${?TW#5sXEorgr8rAKc26^B*?)6{!U%ikZk#`H=GSgzX= zAP0hQTF!m@W`iD5`6S=qRbM9O%RvQw=T|wgZbD!jG#eP7`ADftr_0Wm& z1f@xxMQYif3VOIk4=A>D(ORoKARVFyhX2#)WezQN?G=MB9`C@EecM_E(Gz3kJ9a56 znWQhOeeF$T$z(2W5Hsi&+Q0ztQf) zk&j8wTIV8hcRZcacvhZpGi;LgOJDGk_6ScC7?-HW@3zHE3-)Z)(&;}h6{nOkHpp9! z#%*Bw$j159`=Jkf=gzW+mA3VZ&(7M1xgAl%noGQOBr{rNH@nW*Y%-{HYC`vaMsCE( zwV$*zP=TpH_!6FiEcJspuuG)$j5D&5)4Mfo?`?miEafVShZOh2zE?lce~`I;IA)#b{8L6f3Nxozh?S8H#m!>ON^K^*&aGMS`r?Je3$IB zF1XCfLFfjy6j#z~bW_`9~W4-0+~1eaNOy zIn`I05fSNIQ)1jFo8BhYp5P`vEApSY1Ox-7;EGG_PV58yKd!76xQ#hEQH%Q%g}&eY z>(huhpY7!=nFba#IbeEwZ4ETD4b{fBesj@ln~V@_h}m-!QGayw4MPCW^q>Wu9K99f zO}K$JKW1fOFQ(c6`Be=2QvR#>^&_p`S)xx-M8lz7$bFcr>$G;+~9?T4mzcTMjlT=Ij$rr1v)aqfT*T=^f z|KMxr_M9S}LfdJAW)~*SPZuc$3?dp&&{YzcUl=lkZt=3M!Nu->m?`Bi7wvfxFvLIF zL-S}-kg4<8{H{(78s1@^m+AbeU8g?E8@-3i;j3JidEeM@IBQtUPS_**0^Zb3)cLx+ zpt&Ibs+tMBsCA+(0OP0Bk@0O#FiiQR)_S8=1ejhdmVPAy51>S9b&4skv+}$Nt7(c2 zYdsOg%f#P1hl$wkol<%-M|LuEf?8?h6mbX`z;@|;0aCAS@@+)&rq+seab5YT9z93k z3V#E6e!MF9vp7*)yqr2lX}et1IgWB)n)wp2}UsAVmf81I^X? ztkq@(cVk#Wh;me#-&^xNv;ONDU9!f5vEtiB@SKIR&XCzWYszpQDwrs@tS9DO6(5}5 zGsqR?qQm?)z4%NV{gWZ(xrSygHo1376IZBF0-;MwgO$+dXfi6-zv`vHe2pjRxr3Wh z%u{&3v!lg0&LgUP0tgU!bjOCK;6i_gKIbUU)w;-`qdo<>Zh(Y1*S}VN5gJ(|F0}I( zU`ad)prF!!0f2>m8*w{mlW<1w7t^t-zxk~~dLy0y}pC=b?}p(Kn9O1M*b^`7ax+Zc~ffp|zy zaYO}@QM8d0dmKHT3oWGrJ1!XIR&55-WS;t)+BUGM&+rVahWUv>7MuB!ZT1ED*X#o|6bNaI>D-W2Hj`j)a_Q^w%0;cw6%*(|rRrK&6M`kpes%1|vY!0pPAA(#AyL79 z%}+&{WlamQ&8ht9ltTUC!-g3o+A0*tHd7pulg4I4pk*tI@@@RjRgY2PbYMs_uy4dW zHtAa(K0FU!Lr*q2$iS=Qt?TkHp06pKNnUG<*AX^VZH@ zw(<4sM?ZCF@V`M2hg_8bd*1BZ=63WHGFmtqn5tX4yr zO@cY}sUaDOWr}N`I?dRr%rhi+0a(no4(pEz8LxOMDi{o~_bJ!6KCqVf)GvBaa}l>d zm||iEru7sf(7|m<=p3m2JyVlH zkHd4+#+yh_=Sn}9V)5(RF!Q5p+^Z3PcEp6^W39aiD@CeX=>;qxWHde5_%CS-Kf=dJ12lK@jYYX;6o zo5Fub8WDGjv5c0*6Z{T-8-*HooA4egKv#{d#bh@PUxE^w<6lHVaCpaXrOwFJeNDh6 zex)cj%;d3M!gH;(GT||jCHrBkSV6F(8ozY~C|AuWVHbhgC0N&#?ai*y=Q><`&udDU zQpl7*wTu#4?&9+(WLe$U@{Rvu(cr*v%4}4zt1e|UH!k(`?KOsg|MdN ziHPy6kM1g@pteg@F;4_lpH|* zoL#}Hm`xr*HxC0fp}Q$N*Fsc0cjL}C4f(~=!;kfhDlzVFE^d}?IWBFi9wb#T%5=L> ziT;s(3ueHFaJg(T2cI5|^b~Tx5z@;-I|Db0QyFS!#J%mNh({g^U;kG!2)&_)(wlfG z^DDe6+wIn35al>|(4)|DzcT9gJUgDKbU=@|Yso|vn0BVqW8pa_%3uGNq8I>1lF{FX zXHVbz$1WkSk)UVf_O}HWezYfZp|_WIujVMtVV*!Zht{p6E*^>p{h(BsaeO?cO~Z}2 zqp*4~O7pElwyV?jQxFssZBt#Tjn6=wAFVGLem!Gyi$hZ1P*9uYSM-MWRgV4Er+E5# zwKI%-BzwBKRKMIvcEmDf26V@m0`Iqe9mRG_qmcmw>u?>$d}XEA*-((+zk2n40U>!A zr>%e5?dPII!&Dmw7w56tq-sU2xfgX$af)#=d9JikED+UN_r60>=VPf*@?PNk@7`c3 z$mjI%!#$u)oks2V(LfN+((zbUEd2n2Sw5hb;Q(%S0L6m2edrOFxh1X7-WL8s2GLQ& zx#7!6zRJbGeb%~xy$1}Qp}C>0T=L7C>VE|gpOGSSK^(Q&RMCiNXQ1{LD_{v&f0{6D zqphEhS1gaK&ovd6Q7^g>@?;?ATk}Isjt>pn^HQVsrY=G=6`iY6_3nV--(>~NrH`O@ zk^aVn8l50_f_M~RczyL5(aEVEOAl|IX8j>j-L19nI}ofJy6uA40cmHU;|`mXB z-nbEu5z74u`95#m)KLKy7+T?D9WbB_)IdHygq)IP$M-4H^ z&V?o)gU9DNZ42|SSQIGM(;L|;Q15_V?N)7r^KfzAqYd4$3I4QKi_ik3pZ+shxmY)| z3LN#thvEj7d{V%-r34_tdfUXtnPn>G9Nj4(w3<;m2=eIyXlhsdtq1~FWhpb)h}XFw z+u%Nu<*6(FLIoRpqipdz_n+s6P9*uN7OTDMH;6x2ACC9wR{YJ3mYn&WHu{wY=?gL< zM?9d1%l6QG&z~PHB)?YPwN;ZYlcKXRe+Dq*KEYI&M@~Ej(>r`hEFeDmb^j@FVyMl( zOFpWsKR9@jwliN6M5Lpc7Brbob-FU+`v1uOY}Sku`V>7*p=qAE(_S~m+$`r`%SMME z*9vX>Z+Rgh0CWpMO+X)98o^n-(Rkt{8DlQ%eCW9n3FuaY+aKHy0xqXiuI(^q@8nPl zw?;tvl)TWWkWG-?S{ZdUTZ{nRpO4$DWQMl=HbFg*0s=u#9j$<^V;Nv(lNC-&63+~| zTFz@sg>YW0U#eE5w~9|+c~FsqbR%5QkgA%nTh_}%Fh7=1d&?s38WLbp)eU|hMVEWS zBppho{^sRjuepz%m8|tjJwKw7cxa#eU+g~<+UXD`gln#>zT=DrxNC{nV{+!XsItJuV#Oky!r3(}!f@m|E zJ)ma$m3FWEFLDN1g*+%;tiTKB)R+37cwQ?Lbx)nmedd;rHuz8?y#Aru$>C{GyzFsA zuKfV2Dj~5sfYZ(dx?ZnWy*_Y!BY8B3`ol3q7iG5DZo>SGN>rzw-w3JvTO8$r2!U;5 zRw0)g?zih5!nexyh+6;B3hZSMlA{(2dTUvI)mwdA*gS_QKKGKU>mDelBdfJwIeR3|8>752jO|n51u$4MDj3h4jHnDLi7-C}|P}PH< z>VLKeXTX_vVfAX<42*6t;{MnysC=P8QsO^aN47JNi2cII#WZXQ+j!M9VH5-=rvEi@_?wKvTJnGK#vO>f;L4k zCtGzYiv*prf&RRz-*!i`a*`X$`=^@}w?BFl3+?ZVLAd|7rlx!DS!-dwj?$WGpuM2v zQMK^+2Q18|5Mw~`5ysXEx4VX84!{SJI~CYPNeAdAp+~#v(&Y`XGTYv*!Be(mg^>=Qg@M{dr&lEP%kn7 zKr&~E|1D_7dqNt$!*&zjh{K)IC^g_Fk=4tRG?^v;q$UI$xUuHnI}OP_^cZ@8d*uvK zrqhRf-w0$oPqAp)_JqgH)|ct*iaq4vKj*`p={a|&?1+4)I3dNgX?W$@sD-s|#UZ+2 zF=3PeYwU&tjsRl2fHVU95<})Vh-XOHPTD<{QoAuc;oPWwkUc0VI9vS_ zRCB7L6YLsZH~%^!Znr_FvZfg<#G^|-=qLQ zK+w$|)ti|lW*r{{L=W=$e=2!al`=6N*PFJ>_58{wMN+jAP_sbOxYn}X@}&KaGr(mw zuyUbuvpCFZc!;QP@pl_(ZTezmY_d|wh`&+ri2qelY!wU(dNv78JGu0=yK89dN;@%L zKv5)gqTDpV$w^X4tYlRfV$KfMAf)7Oj06yqEWWc(_Hfdd3Q$T0neZ1o6Mji9TpC;J zz~DEB6`DedL6vx^I`_IY5=RsWvzI#a@;baIdU;^2?Uy}(8H@M?W*_5x#g>dXtX>a|6Y~A>D&#A`I>ii9lPj^nFyJWMM&O)m=g;7ftWpq# zSVPHcl34G)4Cplk++5>$3ZNpT*0Wus8gRSwv=Ly8$or&n z_wtG{eopVe58MtPI07bCWp{`I--3qqhdvAZ`iQ-w*#`F#k3m>z&sOcS6ONfAwI}b> zyO|Rjyz3$DmqJsIZV+*Xj1|ouwXJmz-nuXdnCc(^5p5`xKf$ki{C@$4f)UisKyyLJ z|J%0pa+6-CfR;Dg*Z#kXarB_nxB$1en$WlT`H!JniHOR%16rYjgTc+}hsz(^ z$DJ6^o^@ERc`RcBx>z&Q-8HT(>V#eh`jR(u{ehV)O`EPyu-3x=-Zmk+mur3ka)4xe zk1%+*M7EDs8%da;x`ywuh}hW2cv6ybu*jLm5SLVr)w!|b1JQwwF-8bmNj5POwJdB>Pc9)Cj6+RB!6TcA`pf)u+zY@Yzr)dfPz2@*fg^FV2SO~9D!gl4@=+CMBD z1@}99LvFG>YOqk1QM-Wa1f%fW0IK1#SHi!>DxghUj@|EyH;0H^{&43K<41#1{hQCL z`+HNx>1$=}wCWRk{*P@rHvIVuGoEYEF53t`sRwG6X?VtZxXC4 zC#Xo2Q{0fSsbL;o<&z`h6Q%4BoW)Nc+z(-27-t{f&^RT$alya?@@-$#zVm=n9u6RhG?d^+DtO`ZoDZ@w!O zdN6$XLrwA!M|{ZegWP^+*x?mU|5M=t=P()LvmG2OwD;-AH%Z+f4h<}84x$kL%|vyJ<=ecqeh2z%7q1>-@XfnhEIyH@2k=3zfffM(bC7mJaj<2~eR2>R+RSxO z9oFNP$8MX0|JlGUGSx@^BxmdS9~6`!{nHtq);+8BI=YTFNL+0mOV8Q8($KV-LbWQD%t#o-O0dzyQ}i-bqtl|@4>$- z{A&0O7$|<6U=wV4!6;4z>mE@cN(&de(9^so=>E5PEnBE)d-1(Vd^|)eLI3?ge!}M7 z@OZdngOk1V5Z$2l{iF@AoS|hE-Fxefg8Sg|Z$bz{H?m)^OQ4aoIB(?pB{ICfY~g7r z66BL^fcotS*7c3p=#!7p&mRt&;TbS<9OZ#~yI& zY-)S+J1?F(i`ODtbQtw46`+Qz_kh6uAAZfZ+cE2%&+$?Jhrp9}$lp}ld%e-hqY4hL zMI>D4RCJ`{BvH5UAtXCwVy0s-@q#q~7|%@iZ?8F7(QO>=j}~n?&Lx=b z-DANq23U7+L#UsV`ebv3Uh)dxAx6YQIRD33S&4gDybK@=%`t4|!BQpY;C*{HB{zBye&akl93_rMxN#a};P+}a zsiN00=n-9K|4buY8L;K!xnVdcgmj1mUSpC$`-P3$rh5bGZ;gKLS;)~B1@D#WG6Lu zA|qwr-56QNHZqpynoRfI{k^}>^LzgL{pZEU^|?OhI@fib^FHUi&q`%7fLPWFIslIO zSf+?jR{`+B3Co56$&mt-)+vr?C@bD4KfEsZ>Ngi*A$@wJ>L2!C_~9#KtA`+u_skxM zym`r0if6%mtj!N%kQ_>`4E?mTJRW)r-g$84hN{9yby|AJTHoAaQb->-V?hGTS>Q>{ z80{DcCRke-zDJrhAjvJcD!qBgd#px?Jbwdoj!axZU?3gjJ|MpVCN|`HIVCJGDM>N0 zd1Ts3EBnb&BJr6xo4`ZlhK>yfKUq}2_4HqsZ@MTE`;ek$(WAQnHB<`PG&s*Ve8*Q?@|Ib z6s#8b(y}x~bxDpIKR!|%lGAJ-OM@0Z`q3KP?OJJC$H{D6T3=!oSg{;%9nN>~lVq$F z40-1VusVqK>AeYYdhh(WE!u<~^mnL-0=yTsxiF`=yRNsByB@GL+5QuGwB%|r_a+5U z9C#V(k<3`L-cRx1Wq?0PgUpzbi}?xiWi~p+y%+xYGYrlQ!Bd@Ue3;7OtBUw>*IdWG z0ncd_8p2Zw5DSLw1A(a0T#Jy87a&Oaz3;)tA5H(WG%Gfhf>zCyn6IyP{m_GeR0=a- zU7fEKyGKRjAe&)(tH!^&2h#s^j8vp3sZAq{*5xQ%C9Md%J4%U6hzwoiq5lQCA#L{{ zoJOu^e#2H5WW^MT?^0-4R}+sSK9#PrbPq-w5xw7OyO(2-P>@#%J(oo9A7`>~Flu8y5XX(o5| zn+vv1dq6G(t4}@Jbdru`NHV}CA6;A)l4Y#mVu(bF(aQNB6IjYkG0i+u(FD1sz)OyI_r%~_886#Af-IY=D* zp(koAC}kG!crkfOaWy8IHj$2>tI}t^QI0d3zAiLScr7Gz5HKB?`Yzu7)>^HSQGb*? zAsRZtL;o)zj&O4C4d)vxJj~Z8geji-kL1vgppg7eKMC2BCo5kwMZ|VBUdc#xQ$WLN zC75byj`JOpu+@6r_>#TmvMaV}C*3(zsv*0#Z994K%6|Xf;W)hCp3=m}-|k2g*kEsM zSDaQkbLCa($5bVlYZh1 z(aYIx(~d1n%nlr8lE{GO;<9dyZ=b{ z=`u7svX6c{R_9Q=4ngZ7HgbR6WvmX!ZXs-mK_FaNKMlnA1d{^aspof>1s(#--HK1v z{Yueic3G4%dwO$NOD73af2_YsubEQzd=V{SUUdW4AqRnQ@9jeQuCN}qFnIQ#l(Qi_ zc`DQ;UdaE$YVbSM`rCnl1IIpl3K}ZKs&$ujIQ20ez1An>*S7t(2%zt4+b{Q9qwJ&0 z@1XTAjJ7ho(DD8rU2nUJ>Vl1(c<}Bolru`HW``JFkPU&1?pd66Y8eLJ&q8-TlULvPkv|??tVJa}fv&%qt@}D5>_0k~u{KhFfT40Y z)zNmN8Ni4J6wKc}78c^>Y0nBhVN-rKys-S?iJ&JRBz~+qlAWL03_n_A{dJ&ewz~&l zi|Tr5O0#K5V|N#8^*ikbOGys|^n)AHAA{lv!W%Y88TK>uI#s%haZHiwmSs7C=wds~ z`mVoR``>@ph@pij1 zja6_gmPh(O^%{>_6s}YSoD^(9J~S#Z2L}sxzjvUl*)>h|m8McaWE|@CKDgnoACb>L?te2W8BZPT+F%vwIt#}cS&dCBFe zfqU6npGKc%#EQ+L=O-U+RZu)$ap_AqA#s&tq z(Qng1PIa5AFj@8SS6lZ5)wO)Po5A*Vl)kB3^+lmFW)YHdrMR3pssUj zuH5}wPd+?>r`iG{AUq&G`uVauLQ*lXfmBwkrazXZCWjD|E0fUa9b%~$k#}A!wOy8Q zYW7D!WwLhM(ZHR}>g(4^c%^~kt65gpF3~b9M7t|J$VHK)NHmC2QK5wpw zxj#Jq{dl$0yIAxcJq?D%i{D%_0#>Hhg&#Wc8}P>~WWRjEK&he*A+OdA-m)_&&u9fa zHu+S2{!5xI4;{qY<|->VQqL*jjjC=391>R{Ue2IYv;TV^f^Iv6(@XIbT(HRf+gzJ9+4$-CBz$(%?MvhDaI-4N?h*ZYLIUj~mFTQ7CVs8Z#1 znxZ@V;Tz}q`^EJdajcrR}=p@ECLkqq`f_n*7fuD)@;i7|1 z`dPe_<&nUYE~%WUN)gk6en}>ftwt6Be;eaZ$H(9#tMB}1r4+xE+vp8YN@n@$``6H* zt_|e8WCy`U?*&JQZRgJ_YJ?1C`3f`*IAT1mFkM6sRNkbs0tUua4s1G z+mR6SR%Lzl<~6L#{?6%p-6wdHhDN^JXHrY5vyF2*){*D(A+qobwnv&M6|M=`2W$Pa z5V(4EQ13`c{q*(`?ZK?G8X7>#dnRk%!?Y_!S4EyxbAVFeiA?>))A=m-3s_p^M}?Dy zY*)AH_@KU5BGUHRaq`?>T`*cj^wORgxYNgsu^AC7dVSQ4>tTH0tvSVIVF?H%2M}5U z-zCpbjJ^4Oxc`x`wChFs)ACh7EZcx?W-5QKWa7T^>k-81@h9|wvDMN!J@(A|P(n_^ zz2V3K6VwCZrMvM4&320VstSsufa5A6ek{v}WA!#Yq!AR-{Jdg}&>ZS&JrEn{kgPu& zW+tVgE0_E(B()It#p|7e*cj)QVV`Cob|RkzUlL=3A=KXtL|44V#5!DLR7)iSh$v51XIz6RMZ|xM1iXHDs^jT$G#aXvbD*#VJjk?%Sd@2UKHj2XJ^11+ zg<_9Fno04#=C!<#=aru$0UZZF^|^QJrs8aX29S#ZP$p2F8(+HzP^~ZR7jRP=fTrdv z)zbQco8)D@prQEs&rmxWNZ!7^0ba~U;Y#OX$eS2$t@9RdLs2^zW(ZQ3YED^OOpj7- zBc_9g>+%Re6cy%$gz@jm6`5)*NKn1*8K%W>K~Z80aKkWD$DFC{+D0?>sqw!`yWQ{q zV}|%Gj?Z&;GRT%hGV#u+)Hfe@@E_pP?ob?VXuZwrqtVO_XdVH7-cP)4wHP^mvlHuU zrSJJNq)l<8*T?eMQ-vu3kZaXGf;)>@B`>s=PJ@8%DesZ*i5Pv4w{zja@Y6-l-8B~1 z+=N>(-#a^wLm(4WVU!bsU3tz6%IGG;Gs*MoUUGPu6M3ozuCgp@Njh|kW(NaN$GHWE zDKFQRqg3UXgWQ7G> z!-QICpM=$Foi5uy?oqd_)gd_?%fgm>4Du_)2bnPoiqU`^oFu|F`RDRE@=~iJl6gk3orgMUS zv8$gCs72S`SgClO3b=i^(sn&BLOijHiEvPqt0%n#Td3C64#z#!B zQ%f4kks5Xn2Uw@^e*X_B;g6qHiDo-Fv=CV;MxUZCoj=8@aQ=;)3Bm>Mp)u3rj`nZZ z*vXveMb-8(zI%0i)H=GvUGrSFJ1Wu+fBeszQO>{?HlW|ru}jJ74_0CPF9K_6s?~`- z^Cm`T23Kz%m5e?TKXvHpL-A4;G8~lPUF)IcR zMEaZh-+SNj_-nG8OvlJI2n0rj(q~4O@2_RgEF()Y`rWXIW9lx#8a(W+b>`6FR|&;t zS*=W#hgwX-;!FFOhqHy$0v~8Q((@go@Jbp}6|a2)jNk;=*kIFFf|<;ufsU6l>@!$ zb+~@h<}l(WZX8|bcO4kn#LURlF^-iRk2C4n>$+Jy+&$%QI{$ z&HI%`ibxiP7tSw~BN=+!^GX=$WvG>iOLv&!#e-MmEU^S{dQb(=&AY0qgnGV1AOTh= zP+mbeCmFD7<6M>tp>7R&E&fPP>Wxm;O*6PhSflx zH~E|7Hd+3Bk4U#9Z+-%tG$dcy0NW*^-krfkJy1!xBAz*ICE9PZh(_H0N|0~qcrG@! zD2fjyr1H*i4b&M@8t; zx)DhZIgyHSnsFb~DZjs>VI4$~u1}H!%NX9L3Y!CgM1*3SQ{L6yd9}cX@##_RQ1;_> zet}ztM#icv7ja)gHW!P>aSK}BjHVDs^e%yEObpqKLspPIJ-w2Y?FwUXW`&x-Eio)A-#tJQO86>)0#PA`HtpH;GmuMoGS=L1jJ3F4kn6_ zJK!NK+;zkz_qj`uP>+gqmYzu~bn%C)hGej3%VMP8mRWGkp$Ld`Fjrzs7gt&b6MAE!nol& zm*TP=9z0GVRXIXOf!A1fwoY-KSoel`j_C6$zl_ybnTvIYd+3112(t37wrm|sL7r;f zp^&+Xa}7X1e{Zudj(xS7XPz1<6o19`vfZ0#!Pv4I&u@D%a=_BPQb*Nps6x7LvyecH z!dU41Ku=MZifSO+u&Z#jE~&5KSR%~k!oJ=fqXhfN9)mS`OQJ)iT8v(+EieG-soUJ# zy4ETSmQB1L9-paScFsgD#Uq?lWy6P^&SR;WY7+4s;7B})){|ynHvGOQsy=xTKa$LZ zDQys#cZ-QG1@SdIXEUjN5J26vr^y8uV~%`)8ZXvvP^vH|H8EXY`&SS>1BLf0>!j&g z^)&Hz4K8=?E*J$&3>A^wcqMpNhu`DGV*rVyHm;jCb<<*ZZ71EkCcV<;aqGUI4AO=N z+pN$(ht`yj;}CT5u?MUAyojg8q^1zGJ*DyiB3HsQ$PxkWlLdBh%PfF>6v4*$!`N?g z!?x4T8k6#~OgmVk8?`&{IZ|ulKQW(fge%&zDr3;#!qTJfRzU5@+dFQ>6A7vuV zexxbLYVi~gv7)EZrpPrV;K>uxjUiv6(FO%=9E6Wy)t%pT9hIA(DV z&qdQ{y@#%rt3lxA093$~giUOKwFLsk2YwG*b@bji+<4Du;VYJXqIwK9N?r^QW}@ObH_t!ksk z^(i#sT%4NT)!~T`9VH)^k&CxY0yS2nP+~q>tIlf9i)%cLCxyk>&vqOjIZ_6qMowgf ztZS}iF-7PK_@x(eoM^+AN9Rv!^4i5cPN{5{`vlE)Sr0>4I-;3i+e4sP0?%c+HCtS_ zo0u}aRw#~GVY0tNujG!J&mTJoHwQgsg-{}3RHr&lU7c_G;MznOb^<@z8T2d#)+bqMWgavtUurt0tK{5 z{!U@AAH;8*tu6x_KV~~j;-1g1s?NRS^k~zSTs{CS!(Y)i>&^0R0lRYX;jf!Z)pEUM ztJD6wr7eKlQ?~JSs$Gg^IjPDg0ZyC?UosmmnHOi)c|1qje+dViDj%j)t`7dCoe(LtP?To`>DXZ|kxkEF z+r&N=;);urjQOJm1_(rg>K@H}$&Igef2xRgxi}u)$aZUj{2`kr2j#8}HD*;>tFmux zfUVhc6h#y;yX>YYim_*-h5ihj9>L+tDB52A8W1vGWYG8bP|%GQwp}#b3e0D~@j2RZ zgRhUNi1>7p7IGHk_)+}uuAcQi_;K>bo2p@mWK%>-*U;Jq5v$Jfh1;{b4z{?SqO&zP ze_U(a{D5zm&|=ZRI8B+FQ(rl%Xq1dA$G*4d18DuYg@8 zMs+z^DPEq^&AIT35jcYkc*ypPln0#-R(G9jtcQq;j# zd{>%18rGG%EjyPxANXK6$WypTDk}*dSQyCibfb<97iC)%9qT``!F6Y&WMKa`s<<22 zgCN6^av8LNCu{EaT?3`5_jYWp$wjpj;cP|JUo{6iQ}J1iAoL@qDg0m(3-} zgHzacU|^zUPDBHUO&TFPFr9t%5JD>E#)=HaoJ$Sv`2lS#eQ~v|w=lOVg|}F`4KaZ2(b|Q$)Dmr6FLK zk)2^yIEK(z+nDI^(&SKi%TedFj% z-$J_3c{PZ?JaFJHF4|c*k4voDnk+V4%`{db1@;t> z=e^hoZuppdP|2#rxWk7LC@{qlD#MB)p5r(@HH*U!y8v(G1l5TucL^+01c=RWDukGe z19k%U5tF_dGO_WC%vVC1-tkukauI!Seq%SJ025-7IME~EVmr^to)Y6yjOegpSQYes zm5Hv;CQtCevuYLB>mHnZmAQoWWw$J5 zEJy}!rL42gp6d0_)_;=OP!?~$maU5KI9>+st6wO~YWd_UeInd<)D2#7kT_cDB4Qy& zV3Rw~hS57VEMgA$L7C`YYj6k`h}l1Uzf@gIQ_}iii8PZ4Usp(EA&%6cJjY3mmVTPJ z@{@A4#?MJIGKRHgfv@{5L3%07l6hOvj=6FB`? zGwWW?R}ZnlCd2_j^p;yZ-_;IWkNQM{IRA_9Z~Lalv~5p6B6b zrB&pSWbUCNT&IX7Zdr%x7zo2WNWZ;R`)RxmApkIpBdWNKx~24*Sm(;9Mg8LvY@oja zU}vUon?i-xp-6@&%(H@k&>9qFQ;r5#w~0vs#u>NIDr&n;^q}Pk_1XFgDeU?48Mj+B zrQ#VNd7uK3Vr9Q5l4cg$|E0$z4Ktno@poLV90NNtjE&?tF;t8^1hIQWy|~r9>0zwK zwKOaTU;HotucZ`6Q(Tn(NLqBfoR$|r5chJCA?57!vGDc!4YR1wzwl3+%xR2eZy;m`V@;RVsCz2ub7~G89m--t2T`>aj}S1puV%9$rm@ zS5rRr)Q5wCPi^yu;XXJ(ZK>MpNAb}?BM7c@vjR6$X(1xN0056Kbf6e?L--<~)ef1-Nq`_y=R^_zkkm==irT9?)%I zKgXZg(BGFX1-6L8#w-S>ToB#q<{CR`pOiYll!oB+s!V(k7 z{jr?0FugGqOtX+W(*5i6(zkosNie^NONTZ?vOw$_^cVW6eRfuSuV?jf~zeQpf{hQqMd@CKSgu=}L;R{Nqfd@URN5MU^R5 z9Ui(U#!_VgM*2qmhs#(#Mhn5XiyFR1pG)CKB;CN8%JT265;EHONb%@Z^8W5)quz92 z^HJ~R^JzR^x{Uo;b;LQp=!h{l&w=p(na|IhVB`RS(?jpJi~5)xT{}Z3S>GH`Gyg?> zS@q~!=c$}lH@l0^WkZa)1ikQsy zMD6Wq@MD-9RTSXo_#=BFqdWp?x__Q);74TY|HZ+vLG`{9e$%D5oa;j-NXwwc18GTJ z7=xx$)7Sb;>q~?5l*LCxgo?3`RVj~SsevF!9#(f!^P?;CC&VP>+5U(<#R&uXu3(FB{SvQcOe^MqPPeMr zhwv!ywbWo1C0XV_U#k{~s{cPsFFkZ#`#)bw5sLD^g+lq-khwd-lHhEGQMKC`pSU~< z_@92CD`jC(^Ys52@_z`1GF*VW=!cuLVGW!D0 zF>Qqw{(Inat(aMb!q!S~M0)(K(`lzXz%~E(-3P&j^CZGMYB!(#M}Ny-s`lzp-eLOp zJ18R}rVjwM`A7XJN^eI^iUvxgJ=xS%q&~|7Z}k!QLC+3BKK=FIKetT^HLV#UK{y{! zJWhPN@~_SNKb~W z2MaKJ$^#(3SN`7y%U{NBlt&7rkN$fb^n-xje}oP;gS2R_^w0I9+Kga2iOXRH(qKg; z{#nswyGhQ1|Kqp*RbgB69!-I~e_L2U8fZ%S76LUW_fvE?CvxO}Tmjpcm3m_5fx`_1 zjsHHU%JWovYv;v*>fQ*ZG}ruP>_Gk(;n{u5vHbTT`rmaZ2Hjl#vkzRpf!0i5DXPI6 zC^l*slk#Q6VEK>k|7=t{a8>=TH69i#qmxI6#*q|g6KnnJBd{kYY1}BAfofC(VeT!* zQ}Y$@4S%m0MQQcH36^pTD9B=6MmcylV8Ee-ZXgCUlbX`J!4(3bp%Bmp@26=1WTq1! zJ4w&U_n+UN54gE9HL(0T2h-$SLnOf=?NnRn=gx?0DDRVI;{yGs2EA}XefcH@PFY2` z>4edMP z>{_SBMP)Sd&yazg_uDjpj@DXPA-0x33N@_e=D9Duq`A)fs=J9TWWI4o{j=<(rj_9R z{T)x7(|;^%J9apO6n6LN_etC)lu$+&HOlhy3IZ&#AHh~J1|4J6?cfDj{!2~pm7=NM zi3gSl!jA{wut2oiFpvt)<8QxV%(c1%;Cc!HVw_{#JSb88eK2r|D&zuATg~f#%E}e@ zgqfDi5#{XIqFbS?UwksG;bVYhbjb0hXBKK+WaNXSK&;aEy{)_&eq)^obPXzFZbiny zwt)KvGcHM@5^x+LSAOYA*fN&Kcj={Oh0tI>%R9FE`Nk21ejIl%t{hNzz2LoJYA+&j zxY;vPBJL>u$sJTHF^M+SifF1^=<<+&%V9*^0bjm{&X)2z_!2*T zm8D*Xm&FVi-=H9W-F2Vq150L`o}CUX672b)G6#|aIHmTs3b?CO*T*?;cwt|dAzZM6 zp|lz-fUcgz3Uv({0GP;oWR)()>tY#fe2pu=jkoyynSGWDy}05<{S;bRHk_YvRZN17?6pBl%%#zQwOtKC!(pIeSlS7_^8P&N132mAJ3pRy2H}3#sO= z0$^1nt~(@9O7vN?9Wyg1*7GtP9m*+fOkyANiAgWIn&DQZOO71S5*IPKkp*_x)UO?; z3C^VCFUQ!g@_)U9&Oh>3V@lS-UuV)g7ayN@CBqB~w>M)*3 z99%DtT%t&QoR4FScfCSseeE(>(9K^9iv44OQot}D(%=j)nZ2I zwTrl~^)}zgDQnW<^Rk>mO_`4Gk=FDRq>-*z;RZ!NEsMlA-_PaOxN(hdVwPpZN z@Y}ibIRQRowIafhQ9kf;D>_!Vm*!198@bnmw#GxKL{5a=^cDkY;0bNCw7?w1*47k2 zZ~o1`{kgzB`|T_MCLUK&z|D(ydsI1ehuE1B{S(}YaC<<`zGVqY314ljGHO?v1Mc!v z-RA=n5%Y2a%{8{*p1}tEQYC!m^60}yPvF+a*rkGMH9_mjb~e0%*IX*o*jMPW^^$}` zL}fbVoHQR1Wt%E64|`$TRi!}Pu?l+-0{iXg1-W1El_r~HF(D;5fj6p4EtthphhCZn z>n+(SR)WuDgtERRL*!k(8T`Cgvu_)aZ27OXqa5Y^Hi%xD+VOUd;<-k(77EbT>fAf6K^0v@Kvj* z^gQ2tM6b#=ZfvLsXx2X`5XIPecg)>81}X|V5E}F2`_W!$ouk7%JPM%Un;@OU{@Qu> zId__QfyFG%pp^I69`0mrlgwrOD* zk$YLFs}(8u9^!FrQ#Ae(T4FL{f1%uSl<}1o+zTMA_21R7+-vD2y3UElWmE_@eY^&t zd5;MSWFuzA-+OoB%5&Tm^Gq#GvOnKpL5HvZ{ubTIrWimv?x8>Q#8X~~MSb8wgT`-S z1onhKArd!8xv?9SHptL_PJ{pTzIFMQ3KyMh0yIVUWB)Y}SdR?~limMz;VPt6ENx|h z6A_SH3T|!F{3B}0VgKJbU2i%AIDlaPzUaY)P6ph0m8op7jQI~E!oTkcaO7@m{Q+J8 z$aZ!T;1q&|9TPP~@KV!O!L5QTuTyo0;xZ`K~$34D3X zKWP4qsc)-zZrkt`@3y5X3SmUZMZbTG^yH+bMz^ZcO54d1p%%Bs-MO-kDbjr>C#R{Q z2_ZmC-Um3+)yQvd#6`}J1Nz&O`~IZ~z^WB_BhL;{1Q8XMDtWvHV2E(a-8Owb-(2r= z1hG*d%RrL0te6`rGCA}oFisR}^tUXUJu*EK^NXal|Fr{w9bIhirUK4V5z;s+U)U{Q z)nFfX;Iz5jMOrmD+4-3Aw}Zj2GoOPRi{JNu&N7z%B>S8zy(8bMc%2?SL&n102z32b zjcws0zF|c;2At8@LC_Y&!%|^IjK4FM8YGyo$0xhaa@eaWWuE%&P5{Pwm~}w)^<sWr?#)8W#lcO5B*kTdrFjv<5u1yyyZU$LSi_)JGWUz5PhvG@)h%T~1+(7k;(-Lw^p|*r-{+}gEY4vp2)g|&%Bm@EV|CD2kDu_;!qVD(U4zJBds3l97WC3u9wQ7SBh+cSi4taS zs(`!sS5yIFe2qEcObiA&%h&S0iP8{0DEBCfy8LKG*Y>R0d083u)~iLuAiuj(S2j-- zzimNkOoD4u93_zXuxC6Tbn(En#9$hE7{s~Y`(YU-u>NQv_W{)0s5;)mkz|R9bxcfz zKZkVAo7g-?sd)g}#=q?gyYm*%=c2|UQq{AyTl!tOP0m%(nu#xL5D_kQ^g(TARcXLX zm6f8Z9G;6+eL{QxrxQ`j$jL}G5BpxwK)4wbn>{c-?aX;2_qSO=f6k|;VGZL&e?Spred5>FddeI|?2mhl(A0|cJ{QaMts zvFMry-Qv8)g7-`j{@!iH)>4Co9{nKS83MX@?7^0)&7);Qzh6jUIkFq>iaR_19A#MX z|9N+~Fkw1;Cl=ZNKZkKyy3j8KL6%VsRN!C#UqoCr6;-z{Cs~Jfe1^DuSXZ1bb#+U6 zJ)o5QO<=0@>b6zVuzK{{^uk*{(Egw;bdJ#m{oN9e|}-v3Kqmnofi}7qylar62d(eUYfvk)L0CbSwm*O1(mk zO&guA0ur3w{C9`-f#$1$Z#?@H)Y8=KPlgli0>6w;zYO)Ct;p|O_ep{yGYArN%B<*8 zyAy^_uqW;mfJ$X5%78@>SIUcubjHEwhTXD&$hOH8kC3mGGHTN@%hE#dAT8%`)`LmS zzDBEo0n~VeL+?PjO!y7dwtDtdr9&9kmF4_+~%YGkbg0j)eBRr$fg&T(FcrU0h2#oNj1f z+Dt@$op~0-gjvlxq#3IJ>LGxZC{c&|0Mf{hf={m3+v{5vlf*5oKx_G1Jtv-j7a0Ie zSXBuDEIMK!Ss^TK)wM$Xn-eQ$UE9>tP5~^++8(4mrDs?Sa!2bLTIZles>PLwqAY*a zEn_7T5&gEEVazUnpsv>iLd#gH_iw9xU*v>9?#KQ-y#6fBc3Tl(En*Stx)y-#$ywub ze`#;)*;kK@F5f_a=Y_VnZ@mG^c89XT%e~q$B7=qZjSwwloxX{{+WC18WofNA&P{ZHZ%;Uu`2InU+QzKl<Naqx+p=3^0%tRqyXsn&yih4ZnzrUSj<=tk0N@7+|muX3!npTR-zQWsLfFt z7p80)J}y+K!6C>tHWP_imW^o?sLJK%W=f@LVxgC#1djl4#<5U_nYankiQnkZw4c8R z(vU){Mi7;AZ_S5qmw<&=J=+0WwU>wy%;Bw5lrS;#-bXC~$pQCV48Mc~^V}=AP{)8?XN3gI>V-wwlcHhD9{t3{ z=RfF2IwHRAOQX$NJPC1S0K^L)PkT0A+Hx6!>(EgRguGl;{LFp{j)eSNW*1*H3IRI$ z91^KN6rP5{P84|K>#a`Fh}%`rxSnX!0-cPYwz@dyq$YoR4?32;glI4TWb@7&R?wly zTn3VwZNw?Y`aMq16?^7aW;mdAkkS>N)?vDq+R4qQ^nfNsH7y*08Roggt=9e)9ALTfzKru$cN^8 zFJAW*i|(F<8Qgor+JB&1i*R($0zzJb9*c&4StqoLK!W)r0x?F9LcGn-75<3_WwG6~Jzaw_{PNkV z`j+)hWsD3JYi4*)Gjq%A%sy1E{!8-_?9)@%$G&WN@>x@BD)5uMZ}VwqZW-S<6u}mi z%N|O$pYt_4Uox=w7VJnx!xP7qqTX#1KwW`h=0CrZ4ZBw8dNkHt0fpFBi zS!Sp2=!;_%{ndmh-y07D!&3v_t8qN%|7vA=v1m`D?cEtOP;uLY{)B+cMDL!#K!ewK zbsPETAZ;`=A;xlMu%Vr8af&$L~zf3XylBx<4Svdqx(r z^7w36{CE3xn*P7Q`9H_T!6+Wxn)AT2VroRwg_$}F1u(*8HRhyg4JG8bjP={kLW28G zh-E0bYwlP_RP0@{VGCb!^V*a_S6;%S9OQSi!w>IsItl+uH}FpWJQf>T&j^xYM0L># z;HCEk(w4*5ssDT9>T9UhS#~#J5l1kD4@fzir$(8F%CBxOXbeQeUw>l$cvplb%KWV_ zMup?>wO&~=g`)gt2Jut5HRYYYBN4_iPV*4)FIWABk@4|{&2t#vUvujW_at`6AG}ue zY9M77NfTv88a?6%IsfI;?oLTS8FAsF_&I5-)KI^Jj(a-=uV$705ablmpF=0OV5eCj zxeuZGRU$RP->mz@PbyZCf&vIp&Q7Gx`eD#9a68c+aht!-dDyjY%GoAG_d9)a19`OR zhu?wA)XhC~((~hjM$d~MZ7Rn;4F{|2Ui_%G@~C0Hs?%X;(;%sCGcYOb=Gz8c!-YWA z&|9ar`vwXA5%(EKd{%69+pGtGqdR+b(H9fx}{KunT zLdbmCP3-VazTNbFE7DYUZXLnRK)k)QOgQ<2?M^gYZ6{A$0^x0Ysq+1R`MM8ChajSc zy=tD(e_A=fcIgSsf4I@}O9}M03d4FUG?BJRdR7=k@)Zr{FS^_`tbQpx=M%I&6F>j% zjAN8dL8jLGfgG_4Z`VV=2_vcA8u;@{2l#x0^b?mGCE30kDExuUl{5oaxEOnWWPywb zH~3ynMs0M&8f<(S33X>pc(2l9TY14sg5@#x6)+x69}L;rCJ&87Y^=%e)3ZJ=uy<;C z-~HSX8=78|p3n+?t-_I!Z;)HMJbEW+<-sip+121V1)fRGHTR*U27fypo56}9p(S}f z)*TZz&?bJYqyNaxUxS5yvY)Ts1qkK#*jGwFxSz#ce(t@c&dm)Qdvj;{r4RS2PnC72Z~yR@+pMHndGmF(`-`&<0gcsg z{3t3F*ma|98>#o1AeKxmpg!wrqh*K4D~yQ5!E$}`=9zuO`KIn<8x%>wW8hlA=#Xa? z>3or*jKSrOqimM(4O%bus-q#*4(~UID4$xzf)7(1Jis1YHy_g)7Vm!qUCH|#QFh;n zhTv6jSVodoEzZ9kv$4j_SX9Cm7`uLI^@F9S&&y3p0%{l<`l5k^#%}0pXcyQ2A#Tk1S5JzaxUHWWo@M@ zfTD|G<%aWHc48}pwJOoRN0qnbhQQSW5?^1^OK^Dy^^HeK>@(&~VBLAMh(%XhuVAI@ z+o2}{6-qb)iu#t3r`%juW3Gi?Hk0Z+VihjSkjv^ny@A7QX8J(Fm0O`fL4vPwqiUu# z4fPeI0MdX)k9b>m$`v!Y)a~6%P!U!^c_MNBqJtyrGj#cEjrxs(8EF zLe0|ky5-onn%lCb_W1kI`K-&xj^}yB2IbaR90MQ=i%dV4<{eB{-Hi7USUC`9v)l^J zj?%wwRP`nN^JHbNL_`dGje5j8q>t`P_)@a{{Y=^si z^nt@{P^%AiPdz!?|r70LUK5JUm+nG8#5>0K#>|T!gU7uz38a z&}(K6*g&TK*ZEOJ8FsE3J1nSh%R)6*D4*?BILyUtQmDt_;)_9CBwq*Z>qtJuMHj>+ z+2^=<#Ky=qZc4bObHNIYhs>Kbo7Z+fNa|gWXwB4wvoLvW9`{p@k4WM z@K2KRt8Z>}IdHvaGK~JUJac2I9fH5a)+fL8!4)>QwTe~5P5Wix_tZ@7N-q#~Y z{*Pl}@A*M5*%^@huaEI#O?v0bbg){jzMTy_@t)~jqss8n*!7jX7sa@uce0b{bidJd zDX{^?H-!8>ohg}W8rprhnyqc_`~6Ksyx~cz8vpsP%AMDi*diaxdpm-$_E`?I@?2@O zB|m;xS5`K=RVGKVjWJbo)xe>@a(y%aYR+OdsOarHYw{P@~Q-aqc!89CnA z1IJk~_S{Tf`wX}eS<%)cB#yoI{eGXPu<7S5_gpK&)8i^Qx%Ux|yBjl|x6YOB9f)a- z%S0Kq{WK6xYF`H?%zf!&8@mRFBJQW(O>YftaEC@Inn7r zg#Hfqd={=VWK%iaoMV473kJ5PAR*j*J1Wle7@UuZQL9k-L|H`thpFiCsx+MK0SwUuzr{) z`ebmA*vgkw!^V7G}Oyp%+w>^CY%;yKcASV zg#WhhyWDGe_5`mMTxqThA)ndTOB*HRS(GPRe3B*He@VD-)xcOQ;=ToVcK`bT}INWGb6~O+4rxY%0@-er{gDUSKhwDO%|K zU2{05TfCT24qs6@0M0ZA!S$+x$~Q)bYNsqzJ(Q3@SwpN-;ucf{eg$2V^26pg)ho}?@Zy)B}`!~GvW7^)?XMcO2y}q^9mo^kANVrap3cXP}RQiov_2Xh# z|4_ZjHQm|CZw8V6p#iV86@q|Wy)6hx;eo5~w9hBI-nY&Q$wd+_2Rtu2+G$yh_q8GD zyty9b>1sGS=z?2N7X1NCcI8S_-3k`xO*4nSddT4axqtJn?1tl|Db{x~g9_d!b>hH8v!ZIFZNho?dkeIEecKr5~rVQkahdaxg;uy6) zQ6smYPJP%pI3(PA;l5vc(Y)*Q#bR|JbO61&UAvlir?T_TlIi96IZRA?H^Knm4Ilrq zRaZG>K#&ah-KgwhDBse1pM#9qx^`xbYk7?Ptux&)hRV63`LX5Y-F$~Qz)$dIgaIF= zXIFm|wTF(q?F=*0=;}Zt?n8Wpua6va@!Yp3bUI_(spA0GefFf{7D$W)MCiE12_}&N zx5>n@!@V|gnfpSX_jKH~sDFfn%T+;lUk`>#cx5wb$l6Jub7OPdLQ;o7w=koVo!i!; zg6?_iB#Bn0B+FtnJ&lq431r~+Yu0`+)oJ_y`r{k+3h1LpFrO!_v@!ffB|?vN7CBKE z6-WP#7WtLT(Cxgfic1QWEj_RMJ4%tc@9o|Ypx56KIgJ4Y2jIrPTCJ0h4k=eBUJV!c zoX}yeL*a`RV=VvSFq&AaVLQ_OaZ+l*)3z~CHvY+}a*-tv)n<0vh&_FE_IPQf>h#)6 z_Dtr;6^%8_In2&(!7PVP(H@iJTE|B#3vq*|?>@DY>%EkhtPS=zhn1|e-0ycfB0w?@ zdVKpyo9;5p^7CoV>4k4J($ppEN zWa~=p;m*V_MnvzW=3M2Now5F}Ma!I~ueDAD?%MnmmsjTzN^tU*wq_xkd&`wY~%iV?5Pd47g#B1M@HIhEA7FA{eY`5@&rdm9B znj+c13IA!^p#l6G$%xz1lf*A{zz{3#yo=bV{T1o^Z}U(%u{T_zye#K&%my+LeH{KA23W-c{D|q#kJx zPyZ^tZ~1ZSd8u$zoUf9o*EX`*M=W>=rRp94HRz3v+bpDHPvF(yVk?w1!cqCu4T zD8|0APck+k-Ezr4aRC>FXmaYn#vrQpq!zz#eu=_inqC0cFJWu?sHs}8WzyPgIilS@ zJT}&=fi2dcteC&#z0--!pLDg|nG2ifL!`OYI%bhmM90x^pW2mvkzTl= z@Gm!Y-Z_SDRgjsX#(;}mooRZo&G6Bc%`@@3gZNWK0SXzeEe$aGt+DxJ;^^Tjud27g zz2`@1I?P6dnu{wqH9`!%j6b?`p-3C=Spjz1avb~ciLr+MxkaracH~ppcHuoK!f2J~ z+Wrg_O8dp_ka241b@rXT0gLl8hZrWu!yVC((YzABNZC@x2B}Y zeiY8>S6qG1msz>8-F-7xnc8H+b?nlwW<*O8Wn4#U2KVFNDwORk0gx4Brl?uFoCpL_ zi$Fvn%Zn5Mb~ETze?qvhIThy)!w0%}d_a#qzBt7TIS2Dc z^Q?n)al3t4(H%#8|JE|$wdi{K;fp_PVsBI!^@2!0u;y#69D!S!&aU3ny?sGn8(Ihi z>dLreX=yb3VP)Qhcr}T z5~&o5aOrdO&g@}u_JraORS3rwxN?LZThiKvRcRus%(!NAL%Xn4wTN^}65Dk8wv{7l zbUpp?)QM>nz$<}%L+e(Ue&CRZm5c#OG#u}vcRzqMhhIOz#KFh3>72kNn-Q- z%J|AYz!Ab{i%o5Fk|VM8F!|oh5}mt-AW~LUe(!ibg%TBk{k>V+HCuA9j9(rbaZIAk zsRevX$Q1)xWoNp14%b+2a3EVz;#%(&WKPK%{X)c4cjU?y>3W#;ZRTOyJEnPD@zwS6 zlLZ@iNc#{+@QkL#f+^RG>EOX-gfXRFi~SD4ZZXhWQ@)Wn4=6g2e!%COo07Lf?-t50 z#oPBKM61ZXRcI%}EL$kMFnO|pqeeK@ZN81J#Va_Dk3czGakwC0!=o0+B$aY;e(>zFHfSDCG{V(LH(D*~mHai)jcRrSfZ#Ni z;6u)O|0_Dx+Ip?hE07`y`sV3h}j)QM@cAJ5glV{O; z#+Q`unwrhK2KUzyW5I0y192=ntfr|Y?Nde9hFagY&EAE`N|%{s?Y+JoNwVBviBDh2 za(Q^UXP~roJhPFgu?qqbdi_kF!Z7DmM^}tF{GqyyW^bC0LfTd^HAm z=K!j@7ypeeUoo3Osr{GcKe1#jOKZ8_?WMicHolPv1WCj*stJ?kQ(fENmA+hmZ8@6Tx3IQb4@=Gv>C z*+F5`2@WA}%_;D`&XBEv8WVeD81>e_2Q)B`Ae<3!P6ram=Xa2}s4^lHGEx_0Y(Qxj z4Q$DgH6HrgW$SbJHn39cW&Exd9~l3kBm*SYvi7=#EEcztJS%B^(y;wFB(UKo!vVGY zL*TDwV-QpK$e%+wh=vxHZjqO)(bl>*dedQeW_JXpdLRUf07EDJYIR+)4u6;I`6YLJ^SRaA^ zk@$vevM~M^T#l^m>*QCE$jZl%`I(9teh1=zKK_~#-HGR=$fbkCXEn+Y1sm=|BvZOx z7S0&~c}xvJs^~&r+MZ_5KY7gNsz+qH6s`#Hl&E7N1-QTT1f|MExh#P8K%^?Vp4h5z z&-kl!z|evB>1OP&a_jP?K=80Rvdy$Kc-%z#a~TppA`p?P!|M~25vlp9a$x~Oaf$^4 zSpT6&V(--tL=Js${Lv@~x8NUYOeY4Cj`)7ng5tg5D4_jf-#Az{EM(-k9tgW>vLZ(|TK(G*^?Ep@`zpP&Cj