Skip to content

Commit 26418c6

Browse files
mochi-comochi-cothedevop
authored
Implement File based configuration (#351)
* Implement file-based configuration * Implement file-based configuration * Replace DefaultServerCapabilities with NewDefaultServerCapabilities() to avoid data race (#360) Co-authored-by: JB <[email protected]> * Only pass a copy of system.Info to hooks (#365) * Only pass a copy of system.Info to hooks * Rename Itoa to Int64toa --------- Co-authored-by: JB <[email protected]> * Allow configurable max stored qos > 0 messages (#359) * Allow configurable max stored qos > 0 messages * Only rollback Inflight if QoS > 0 * Only rollback Inflight if QoS > 0 * Minor refactor * Update server version * Implement file-based configuration * Implement file-based configuration * update configs with maximum_inflight value * update docker configuration * fix tests --------- Co-authored-by: mochi-co <[email protected]> Co-authored-by: thedevop <[email protected]>
1 parent 26720c2 commit 26418c6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1160
-219
lines changed

Dockerfile

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,12 @@ RUN go mod download
1111

1212
COPY . ./
1313

14-
RUN go build -o /app/mochi ./cmd
15-
14+
RUN go build -o /app/mochi ./cmd/docker
1615

1716
FROM alpine
1817

1918
WORKDIR /
2019
COPY --from=builder /app/mochi .
2120

22-
# tcp
23-
EXPOSE 1883
24-
25-
# websockets
26-
EXPOSE 1882
27-
28-
# dashboard
29-
EXPOSE 8080
30-
3121
ENTRYPOINT [ "/mochi" ]
22+
CMD ["/cmd/docker", "--config", "config.yaml"]

README.md

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ Unless it's a critical issue, new releases typically go out over the weekend.
6060
- Please [open an issue](https://github.com/mochi-mqtt/server/issues) to request new features or event hooks!
6161
- Cluster support.
6262
- Enhanced Metrics support.
63-
- File-based server configuration (supporting docker).
6463

6564
## Quick Start
6665
### Running the Broker with Go
@@ -77,18 +76,50 @@ You can now pull and run the [official Mochi MQTT image](https://hub.docker.com/
7776
```sh
7877
docker pull mochimqtt/server
7978
or
80-
docker run mochimqtt/server
79+
docker run -v $(pwd)/config.yaml:/config.yaml mochimqtt/server
8180
```
8281

83-
This is a work in progress, and a [file-based configuration](https://github.com/orgs/mochi-mqtt/projects/2) is being developed to better support this implementation. _More substantial docker support is being discussed [here](https://github.com/orgs/mochi-mqtt/discussions/281#discussion-5544545) and [here](https://github.com/orgs/mochi-mqtt/discussions/209). Please join the discussion if you use Mochi-MQTT in this environment._
82+
For most use cases, you can use File Based Configuration to configure the server, by specifying a valid `yaml` or `json` config file.
8483

85-
A simple Dockerfile is provided for running the [cmd/main.go](cmd/main.go) Websocket, TCP, and Stats server:
84+
A simple Dockerfile is provided for running the [cmd/main.go](cmd/main.go) Websocket, TCP, and Stats server, using the `allow-all` auth hook.
8685

8786
```sh
8887
docker build -t mochi:latest .
89-
docker run -p 1883:1883 -p 1882:1882 -p 8080:8080 mochi:latest
88+
docker run -p 1883:1883 -p 1882:1882 -p 8080:8080 -v $(pwd)/config.yaml:/config.yaml mochi:latest
9089
```
9190

91+
### File Based Configuration
92+
You can use File Based Configuration with either the Docker image (described above), or by running the build binary with the `--config=config.yaml` or `--config=config.json` parameter.
93+
94+
Configuration files provide a convenient mechanism for easily preparing a server with the most common configurations. You can enable and configure built-in hooks and listeners, and specify server options and compatibilities:
95+
96+
```yaml
97+
listeners:
98+
- type: "tcp"
99+
id: "tcp12"
100+
address: ":1883"
101+
- type: "ws"
102+
id: "ws1"
103+
address: ":1882"
104+
- type: "sysinfo"
105+
id: "stats"
106+
address: ":1880"
107+
hooks:
108+
auth:
109+
allow_all: true
110+
options:
111+
inline_client: true
112+
```
113+
114+
Please review the examples found in `examples/config` for all available configuration options.
115+
116+
There are a few conditions to note:
117+
1. If you use file-based configuration, you can only have one of each hook type.
118+
2. You can only use built in hooks with file-based configuration, as the type and configuration structure needs to be known by the server in order for it to be applied.
119+
3. You can only use built in listeners, for the reasons above.
120+
121+
If you need to implement custom hooks or listeners, please do so using the traditional manner indicated in `cmd/main.go`.
122+
92123
## Developing with Mochi MQTT
93124
### Importing as a package
94125
Importing Mochi MQTT as a package requires just a few lines of code to get started.

cmd/docker/main.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// SPDX-License-Identifier: MIT
2+
// SPDX-FileCopyrightText: 2023 mochi-mqtt
3+
// SPDX-FileContributor: dgduncan, mochi-co
4+
5+
package main
6+
7+
import (
8+
"flag"
9+
"fmt"
10+
"github.com/mochi-mqtt/server/v2/config"
11+
"log"
12+
"log/slog"
13+
"os"
14+
"os/signal"
15+
"syscall"
16+
17+
mqtt "github.com/mochi-mqtt/server/v2"
18+
)
19+
20+
func main() {
21+
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, nil))) // set basic logger to ensure logs before configuration are in a consistent format
22+
23+
configFile := flag.String("config", "config.yaml", "path to mochi config yaml or json file")
24+
flag.Parse()
25+
26+
entries, err := os.ReadDir("./")
27+
if err != nil {
28+
log.Fatal(err)
29+
}
30+
31+
for _, e := range entries {
32+
fmt.Println(e.Name())
33+
}
34+
35+
sigs := make(chan os.Signal, 1)
36+
done := make(chan bool, 1)
37+
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
38+
go func() {
39+
<-sigs
40+
done <- true
41+
}()
42+
43+
configBytes, err := os.ReadFile(*configFile)
44+
if err != nil {
45+
log.Fatal(err)
46+
}
47+
48+
options, err := config.FromBytes(configBytes)
49+
if err != nil {
50+
log.Fatal(err)
51+
}
52+
53+
server := mqtt.New(options)
54+
55+
go func() {
56+
err := server.Serve()
57+
if err != nil {
58+
log.Fatal(err)
59+
}
60+
}()
61+
62+
<-done
63+
server.Log.Warn("caught signal, stopping...")
64+
_ = server.Close()
65+
server.Log.Info("mochi mqtt shutdown complete")
66+
}

cmd/main.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,31 @@ func main() {
3333
server := mqtt.New(nil)
3434
_ = server.AddHook(new(auth.AllowHook), nil)
3535

36-
tcp := listeners.NewTCP("t1", *tcpAddr, nil)
36+
tcp := listeners.NewTCP(listeners.Config{
37+
ID: "t1",
38+
Address: *tcpAddr,
39+
})
3740
err := server.AddListener(tcp)
3841
if err != nil {
3942
log.Fatal(err)
4043
}
4144

42-
ws := listeners.NewWebsocket("ws1", *wsAddr, nil)
45+
ws := listeners.NewWebsocket(listeners.Config{
46+
ID: "ws1",
47+
Address: *wsAddr,
48+
})
4349
err = server.AddListener(ws)
4450
if err != nil {
4551
log.Fatal(err)
4652
}
4753

48-
stats := listeners.NewHTTPStats("stats", *infoAddr, nil, server.Info)
54+
stats := listeners.NewHTTPStats(
55+
listeners.Config{
56+
ID: "info",
57+
Address: *infoAddr,
58+
},
59+
server.Info,
60+
)
4961
err = server.AddListener(stats)
5062
if err != nil {
5163
log.Fatal(err)
@@ -61,6 +73,5 @@ func main() {
6173
<-done
6274
server.Log.Warn("caught signal, stopping...")
6375
_ = server.Close()
64-
server.Log.Info("main.go finished")
65-
76+
server.Log.Info("mochi mqtt shutdown complete")
6677
}

config.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
listeners:
2+
- type: "tcp"
3+
id: "tcp12"
4+
address: ":1883"
5+
- type: "ws"
6+
id: "ws1"
7+
address: ":1882"
8+
- type: "sysinfo"
9+
id: "stats"
10+
address: ":1880"
11+
hooks:
12+
auth:
13+
allow_all: true
14+
options:
15+
inline_client: true

config/config.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// SPDX-License-Identifier: MIT
2+
// SPDX-FileCopyrightText: 2023 mochi-mqtt, mochi-co
3+
// SPDX-FileContributor: mochi-co
4+
5+
package config
6+
7+
import (
8+
"encoding/json"
9+
"github.com/mochi-mqtt/server/v2/hooks/auth"
10+
"github.com/mochi-mqtt/server/v2/hooks/debug"
11+
"github.com/mochi-mqtt/server/v2/hooks/storage/badger"
12+
"github.com/mochi-mqtt/server/v2/hooks/storage/bolt"
13+
"github.com/mochi-mqtt/server/v2/hooks/storage/redis"
14+
"github.com/mochi-mqtt/server/v2/listeners"
15+
"gopkg.in/yaml.v3"
16+
17+
mqtt "github.com/mochi-mqtt/server/v2"
18+
)
19+
20+
// config defines the structure of configuration data to be parsed from a config source.
21+
type config struct {
22+
Options mqtt.Options
23+
Listeners []listeners.Config `yaml:"listeners" json:"listeners"`
24+
HookConfigs HookConfigs `yaml:"hooks" json:"hooks"`
25+
}
26+
27+
// HookConfigs contains configurations to enable individual hooks.
28+
type HookConfigs struct {
29+
Auth *HookAuthConfig `yaml:"auth" json:"auth"`
30+
Storage *HookStorageConfig `yaml:"storage" json:"storage"`
31+
Debug *debug.Options `yaml:"debug" json:"debug"`
32+
}
33+
34+
// HookAuthConfig contains configurations for the auth hook.
35+
type HookAuthConfig struct {
36+
Ledger auth.Ledger `yaml:"ledger" json:"ledger"`
37+
AllowAll bool `yaml:"allow_all" json:"allow_all"`
38+
}
39+
40+
// HookStorageConfig contains configurations for the different storage hooks.
41+
type HookStorageConfig struct {
42+
Badger *badger.Options `yaml:"badger" json:"badger"`
43+
Bolt *bolt.Options `yaml:"bolt" json:"bolt"`
44+
Redis *redis.Options `yaml:"redis" json:"redis"`
45+
}
46+
47+
// ToHooks converts Hook file configurations into Hooks to be added to the server.
48+
func (hc HookConfigs) ToHooks() []mqtt.HookLoadConfig {
49+
var hlc []mqtt.HookLoadConfig
50+
51+
if hc.Auth != nil {
52+
hlc = append(hlc, hc.toHooksAuth()...)
53+
}
54+
55+
if hc.Storage != nil {
56+
hlc = append(hlc, hc.toHooksStorage()...)
57+
}
58+
59+
if hc.Debug != nil {
60+
hlc = append(hlc, mqtt.HookLoadConfig{
61+
Hook: new(debug.Hook),
62+
Config: hc.Debug,
63+
})
64+
}
65+
66+
return hlc
67+
}
68+
69+
// toHooksAuth converts auth hook configurations into auth hooks.
70+
func (hc HookConfigs) toHooksAuth() []mqtt.HookLoadConfig {
71+
var hlc []mqtt.HookLoadConfig
72+
if hc.Auth.AllowAll {
73+
hlc = append(hlc, mqtt.HookLoadConfig{
74+
Hook: new(auth.AllowHook),
75+
})
76+
} else {
77+
hlc = append(hlc, mqtt.HookLoadConfig{
78+
Hook: new(auth.Hook),
79+
Config: &auth.Options{
80+
Ledger: &auth.Ledger{ // avoid copying sync.Locker
81+
Users: hc.Auth.Ledger.Users,
82+
Auth: hc.Auth.Ledger.Auth,
83+
ACL: hc.Auth.Ledger.ACL,
84+
},
85+
},
86+
})
87+
}
88+
return hlc
89+
}
90+
91+
// toHooksAuth converts storage hook configurations into storage hooks.
92+
func (hc HookConfigs) toHooksStorage() []mqtt.HookLoadConfig {
93+
var hlc []mqtt.HookLoadConfig
94+
if hc.Storage.Badger != nil {
95+
hlc = append(hlc, mqtt.HookLoadConfig{
96+
Hook: new(badger.Hook),
97+
Config: hc.Storage.Badger,
98+
})
99+
}
100+
101+
if hc.Storage.Bolt != nil {
102+
hlc = append(hlc, mqtt.HookLoadConfig{
103+
Hook: new(bolt.Hook),
104+
Config: hc.Storage.Bolt,
105+
})
106+
}
107+
108+
if hc.Storage.Redis != nil {
109+
hlc = append(hlc, mqtt.HookLoadConfig{
110+
Hook: new(redis.Hook),
111+
Config: hc.Storage.Redis,
112+
})
113+
}
114+
return hlc
115+
}
116+
117+
// FromBytes unmarshals a byte slice of JSON or YAML config data into a valid server options value.
118+
// Any hooks configurations are converted into Hooks using the toHooks methods in this package.
119+
func FromBytes(b []byte) (*mqtt.Options, error) {
120+
c := new(config)
121+
o := mqtt.Options{}
122+
123+
if len(b) == 0 {
124+
return nil, nil
125+
}
126+
127+
if b[0] == '{' {
128+
err := json.Unmarshal(b, c)
129+
if err != nil {
130+
return nil, err
131+
}
132+
} else {
133+
err := yaml.Unmarshal(b, c)
134+
if err != nil {
135+
return nil, err
136+
}
137+
}
138+
139+
o = c.Options
140+
o.Hooks = c.HookConfigs.ToHooks()
141+
o.Listeners = c.Listeners
142+
143+
return &o, nil
144+
}

0 commit comments

Comments
 (0)