├── .dockerignore ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.yaml │ ├── config.yaml │ └── feature_request.yaml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yaml └── workflows │ ├── build.yaml │ ├── check-license.yaml │ ├── swagger-ui.yaml │ └── tests.yaml ├── .gitignore ├── ADOPTERS.md ├── CONTRIBUTING.md ├── LICENSE ├── MAINTAINERS ├── Makefile ├── README.md ├── alarms ├── alarms.go ├── alarms_test.go ├── api │ ├── doc.go │ ├── endpoint.go │ ├── requests.go │ ├── responses.go │ └── transport.go ├── brokers │ ├── brokers_nats.go │ └── brokers_rabbitmq.go ├── consumer │ └── consumer.go ├── doc.go ├── middleware │ ├── authorization.go │ ├── doc.go │ ├── logging.go │ ├── metrics.go │ └── tracing.go ├── mocks │ ├── repository.go │ └── service.go ├── postgres │ ├── alarms.go │ ├── alarms_test.go │ ├── init.go │ └── setup_test.go ├── service.go ├── service_test.go └── status.go ├── api ├── common.go └── grpc │ └── readers │ └── v1 │ ├── readers.pb.go │ └── readers_grpc.pb.go ├── apidocs ├── asyncapi │ ├── mqtt.yaml │ └── websocket.yaml └── openapi │ ├── README.md │ ├── bootstrap.yaml │ ├── notifiers.yaml │ ├── readers.yaml │ ├── rules.yaml │ └── schemas │ └── health_info.yaml ├── bootstrap ├── README.md ├── api │ ├── doc.go │ ├── endpoint.go │ ├── endpoint_test.go │ ├── requests.go │ ├── requests_test.go │ ├── responses.go │ └── transport.go ├── configs.go ├── doc.go ├── events │ ├── consumer │ │ ├── doc.go │ │ ├── events.go │ │ └── streams.go │ ├── doc.go │ └── producer │ │ ├── doc.go │ │ ├── events.go │ │ ├── setup_test.go │ │ ├── streams.go │ │ └── streams_test.go ├── middleware │ ├── authorization.go │ ├── logging.go │ └── metrics.go ├── mocks │ ├── config_reader.go │ ├── config_repository.go │ └── service.go ├── postgres │ ├── configs.go │ ├── configs_test.go │ ├── doc.go │ ├── init.go │ └── setup_test.go ├── reader.go ├── reader_test.go ├── service.go ├── service_test.go ├── state.go └── tracing │ ├── doc.go │ └── tracing.go ├── cli ├── bootstrap.go ├── bootstrap_test.go ├── commands_test.go ├── config.go ├── consumers.go ├── consumers_test.go ├── provision.go ├── sdk.go ├── setup_test.go └── utils.go ├── cmd ├── alarms │ └── main.go ├── bootstrap │ └── main.go ├── cli │ └── main.go ├── postgres-reader │ └── main.go ├── postgres-writer │ └── main.go ├── provision │ └── main.go ├── re │ └── main.go ├── timescale-reader │ └── main.go └── timescale-writer │ └── main.go ├── config.toml ├── consumers ├── README.md ├── doc.go ├── notifiers │ ├── README.md │ ├── api │ │ ├── doc.go │ │ ├── endpoint.go │ │ ├── endpoint_test.go │ │ ├── logging.go │ │ ├── metrics.go │ │ ├── requests.go │ │ ├── responses.go │ │ └── transport.go │ ├── doc.go │ ├── mocks │ │ ├── service.go │ │ └── subscriptions_repository.go │ ├── postgres │ │ ├── database.go │ │ ├── doc.go │ │ ├── init.go │ │ ├── setup_test.go │ │ ├── subscriptions.go │ │ └── subscriptions_test.go │ ├── service.go │ ├── service_test.go │ ├── smpp │ │ ├── README.md │ │ ├── config.go │ │ ├── doc.go │ │ └── notifier.go │ ├── smtp │ │ └── notifier.go │ ├── subscriptions.go │ └── tracing │ │ ├── doc.go │ │ └── subscriptions.go ├── tracing │ └── consumers.go └── writers │ ├── README.md │ ├── api │ ├── doc.go │ ├── logging.go │ ├── metrics.go │ └── transport.go │ ├── brokers │ ├── brokers_nats.go │ └── brokers_rabbitmq.go │ ├── doc.go │ ├── postgres │ ├── README.md │ ├── consumer.go │ ├── consumer_test.go │ ├── doc.go │ ├── init.go │ └── setup_test.go │ └── timescale │ ├── README.md │ ├── consumer.go │ ├── consumer_test.go │ ├── doc.go │ ├── init.go │ └── setup_test.go ├── doc.go ├── docker ├── .env ├── Dockerfile ├── Dockerfile.dev ├── README.md ├── addons │ ├── bootstrap │ │ └── docker-compose.yaml │ ├── postgres-reader │ │ └── docker-compose.yaml │ ├── postgres-writer │ │ ├── config.toml │ │ └── docker-compose.yaml │ ├── prometheus │ │ ├── docker-compose.yaml │ │ ├── grafana │ │ │ ├── dashboard.yaml │ │ │ ├── datasource.yaml │ │ │ └── example-dashboard.json │ │ └── metrics │ │ │ └── prometheus.yaml │ ├── provision │ │ ├── configs │ │ │ └── config.toml │ │ └── docker-compose.yaml │ ├── timescale-reader │ │ └── docker-compose.yaml │ ├── timescale-writer │ │ ├── config.toml │ │ └── docker-compose.yaml │ └── vault │ │ ├── README.md │ │ ├── config.hcl │ │ ├── docker-compose.yaml │ │ ├── entrypoint.sh │ │ └── scripts │ │ ├── .gitignore │ │ ├── magistrala_things_certs_issue.template.hcl │ │ ├── vault_cmd.sh │ │ ├── vault_copy_certs.sh │ │ ├── vault_copy_env.sh │ │ ├── vault_create_approle.sh │ │ ├── vault_init.sh │ │ ├── vault_set_pki.sh │ │ └── vault_unseal.sh ├── config.toml ├── docker-compose.yaml ├── nginx │ ├── .gitignore │ ├── entrypoint.sh │ ├── nginx-key.conf │ └── nginx-x509.conf ├── ssl │ ├── .gitignore │ ├── Makefile │ ├── certs │ │ ├── ca.crt │ │ ├── ca.key │ │ ├── magistrala-server.crt │ │ └── magistrala-server.key │ └── dhparam.pem ├── supermq-docker-compose.override.yaml ├── supermq-docker │ ├── .env │ ├── Dockerfile │ ├── Dockerfile.dev │ ├── README.md │ ├── addons │ │ ├── certs │ │ │ ├── config.yaml │ │ │ └── docker-compose.yaml │ │ ├── journal │ │ │ └── docker-compose.yaml │ │ ├── prometheus │ │ │ ├── docker-compose.yaml │ │ │ ├── grafana │ │ │ │ ├── dashboard.yaml │ │ │ │ ├── datasource.yaml │ │ │ │ └── example-dashboard.json │ │ │ └── metrics │ │ │ │ └── prometheus.yaml │ │ └── vault │ │ │ ├── README.md │ │ │ ├── config.hcl │ │ │ ├── docker-compose.yaml │ │ │ ├── entrypoint.sh │ │ │ └── scripts │ │ │ ├── .gitignore │ │ │ ├── supermq_clients_certs_issue.template.hcl │ │ │ ├── vault_cmd.sh │ │ │ ├── vault_copy_certs.sh │ │ │ ├── vault_copy_env.sh │ │ │ ├── vault_create_approle.sh │ │ │ ├── vault_init.sh │ │ │ ├── vault_set_pki.sh │ │ │ └── vault_unseal.sh │ ├── docker-compose.yaml │ ├── nats │ │ └── nats.conf │ ├── nginx │ │ ├── .gitignore │ │ ├── entrypoint.sh │ │ ├── nginx-key.conf │ │ ├── nginx-x509.conf │ │ └── snippets │ │ │ ├── http_access_log.conf │ │ │ ├── mqtt-upstream-cluster.conf │ │ │ ├── mqtt-upstream-single.conf │ │ │ ├── mqtt-ws-upstream-cluster.conf │ │ │ ├── mqtt-ws-upstream-single.conf │ │ │ ├── proxy-headers.conf │ │ │ ├── ssl-client.conf │ │ │ ├── ssl.conf │ │ │ ├── stream_access_log.conf │ │ │ ├── verify-ssl-client.conf │ │ │ └── ws-upgrade.conf │ ├── rabbitmq │ │ ├── enabled_plugins │ │ └── rabbitmq.conf │ ├── spicedb │ │ └── schema.zed │ ├── ssl │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── authorization.js │ │ ├── certs │ │ │ ├── ca.crt │ │ │ ├── ca.key │ │ │ ├── supermq-server.crt │ │ │ └── supermq-server.key │ │ └── dhparam.pem │ └── templates │ │ ├── smtp-notifier.tmpl │ │ └── users.tmpl └── templates │ └── re.tmpl ├── go.mod ├── go.sum ├── internal ├── clients │ ├── doc.go │ └── redis │ │ ├── doc.go │ │ └── redis.go ├── email │ ├── README.md │ ├── doc.go │ └── email.go ├── proto │ └── readers │ │ └── v1 │ │ └── readers.proto └── testsutil │ └── common.go ├── pkg ├── README.md ├── doc.go ├── errors │ ├── README.md │ ├── doc.go │ ├── errors.go │ ├── errors_test.go │ ├── repository │ │ └── types.go │ ├── sdk_errors.go │ ├── sdk_errors_test.go │ ├── service │ │ └── types.go │ └── types.go ├── postgres │ └── client.go ├── prometheus │ ├── doc.go │ └── metrics.go ├── reltime │ ├── reltime.go │ └── reltime_test.go └── sdk │ ├── bootstrap.go │ ├── bootstrap_test.go │ ├── consumers.go │ ├── consumers_test.go │ ├── doc.go │ ├── messages.go │ ├── messages_test.go │ ├── mocks │ └── sdk.go │ ├── responses.go │ ├── sdk.go │ └── setup_test.go ├── provision ├── README.md ├── api │ ├── doc.go │ ├── endpoint.go │ ├── endpoint_test.go │ ├── logging.go │ ├── requests.go │ ├── requests_test.go │ ├── responses.go │ └── transport.go ├── config.go ├── config_test.go ├── configs │ └── config.toml ├── doc.go ├── mocks │ └── service.go ├── service.go └── service_test.go ├── re ├── README.md ├── aes.go ├── aes_test.go ├── api │ ├── doc.go │ ├── endpoints.go │ ├── endpoints_test.go │ ├── requests.go │ ├── responses.go │ └── transport.go ├── bindings.go ├── bindings_test.go ├── doc.go ├── emailer.go ├── emailer │ ├── doc.go │ └── emailer.go ├── generator.go ├── handlers.go ├── lua.go ├── middleware │ ├── authorization.go │ └── logging.go ├── mocks │ ├── emailer.go │ ├── repository.go │ ├── service.go │ └── ticker.go ├── postgres │ ├── init.go │ ├── repository.go │ └── rule.go ├── reports.go ├── rule.go ├── schedule.go ├── service.go ├── service_test.go ├── status.go └── ticker.go ├── readers ├── README.md ├── api │ ├── doc.go │ ├── grpc │ │ ├── client.go │ │ ├── doc.go │ │ ├── endpoint.go │ │ ├── endpoint_test.go │ │ ├── request.go │ │ ├── responses.go │ │ ├── server.go │ │ └── setup_test.go │ └── http │ │ ├── endpoint.go │ │ ├── endpoint_test.go │ │ ├── requests.go │ │ ├── responses.go │ │ └── transport.go ├── doc.go ├── middleware │ ├── doc.go │ ├── logging.go │ └── metrics.go ├── mocks │ └── readers_client.go ├── postgres │ ├── README.md │ ├── doc.go │ ├── init.go │ ├── messages.go │ ├── messages_test.go │ └── setup_test.go └── timescale │ ├── README.md │ ├── doc.go │ ├── messages.go │ ├── messages_test.go │ └── setup_test.go ├── scripts ├── ci.sh ├── csv │ ├── channels.csv │ └── clients.csv ├── gen-ts-data │ ├── .gitignore │ └── gen-messages.py ├── provision-dev.sh ├── run.sh └── supermq.sh └── tools ├── config ├── .codecov.yaml ├── .golangci.yaml ├── .mockery.yaml └── boilerplate.txt ├── doc.go ├── e2e ├── Makefile ├── README.md ├── cmd │ └── main.go ├── doc.go └── e2e.go ├── mqtt-bench ├── Makefile ├── README.md ├── bench.go ├── client.go ├── cmd │ └── main.go ├── config.go ├── doc.go ├── results.go ├── scripts │ └── mqtt-bench.sh └── templates │ └── reference.toml └── provision ├── Makefile ├── README.md ├── cmd └── main.go ├── doc.go └── provision.go /.dockerignore: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | .git 5 | .github 6 | build 7 | docker 8 | metrics 9 | scripts 10 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @absmach/magistrala 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | name: Bug Report 5 | description: File a bug/issue report. Make sure to search to see if an issue already exists for the bug you encountered. 6 | title: "Bug: " 7 | labels: ["bug", "needs-review", "help wanted"] 8 | body: 9 | - type: textarea 10 | attributes: 11 | label: What were you trying to achieve? 12 | description: A clear and concise description of what the bug is. 13 | validations: 14 | required: true 15 | - type: textarea 16 | attributes: 17 | label: What are the expected results? 18 | description: A concise description of what you expected to happen. 19 | validations: 20 | required: true 21 | - type: textarea 22 | attributes: 23 | label: What are the received results? 24 | description: A concise description of what you received. 25 | validations: 26 | required: true 27 | - type: textarea 28 | attributes: 29 | label: Steps To Reproduce 30 | description: What are the steps to reproduce the issue? 31 | placeholder: | 32 | 1. In this environment... 33 | 2. With this config... 34 | 3. Run '...' 35 | 4. See error... 36 | validations: 37 | required: false 38 | - type: textarea 39 | attributes: 40 | label: In what environment did you encounter the issue? 41 | description: A concise description of the environment you encountered the issue in. 42 | validations: 43 | required: true 44 | - type: textarea 45 | attributes: 46 | label: Additional information you deem important 47 | description: | 48 | Links? References? Anything that will give us more context about the issue you are encountering! 49 | 50 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. 51 | validations: 52 | required: false 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | blank_issues_enabled: false 5 | contact_links: 6 | - name: Google group 7 | url: https://groups.google.com/forum/#!forum/mainflux 8 | about: Join the Magistrala community on Google group. 9 | - name: Gitter 10 | url: https://gitter.im/mainflux/mainflux 11 | about: Join the Magistrala community on Gitter. 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | name: Feature Request 5 | description: File a feature request. Make sure to search to see if a request already exists for the feature you are requesting. 6 | title: "Feature: <title>" 7 | labels: ["enchancement", "needs-review"] 8 | body: 9 | - type: textarea 10 | attributes: 11 | label: Is your feature request related to a problem? Please describe. 12 | description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 13 | validations: 14 | required: true 15 | - type: textarea 16 | attributes: 17 | label: Describe the feature you are requesting, as well as the possible use case(s) for it. 18 | description: A clear and concise description of what you want to happen. 19 | validations: 20 | required: true 21 | - type: dropdown 22 | attributes: 23 | label: Indicate the importance of this feature to you. 24 | description: This will help us prioritize the feature request. 25 | options: 26 | - Must-have 27 | - Should-have 28 | - Nice-to-have 29 | validations: 30 | required: true 31 | - type: textarea 32 | attributes: 33 | label: Anything else? 34 | description: | 35 | Links? References? Anything that will give us more context about the feature that you are requesting. 36 | 37 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. 38 | validations: 39 | required: false 40 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "github-actions" 7 | directory: "./.github/workflows" 8 | schedule: 9 | interval: "monthly" 10 | day: "monday" 11 | timezone: "Europe/Paris" 12 | groups: 13 | gh-dependency: 14 | patterns: 15 | - "*" 16 | 17 | - package-ecosystem: "gomod" 18 | directory: "/" 19 | schedule: 20 | interval: "weekly" 21 | day: "monday" 22 | timezone: "Europe/Paris" 23 | 24 | - package-ecosystem: "docker" 25 | directory: "./docker" 26 | schedule: 27 | interval: "monthly" 28 | day: "monday" 29 | timezone: "Europe/Paris" 30 | groups: 31 | docker-dependency: 32 | patterns: 33 | - "*" 34 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | name: Continuous Delivery 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build-and-push: 13 | name: Build and Push 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v4 19 | 20 | - name: Fetch tags for the build 21 | run: | 22 | git fetch --prune --unshallow --tags 23 | 24 | - name: Install Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: 1.24.x 28 | cache-dependency-path: "go.sum" 29 | 30 | - name: Run tests 31 | run: | 32 | make test 33 | 34 | - name: Upload coverage 35 | uses: codecov/codecov-action@v5 36 | with: 37 | token: ${{ secrets.CODECOV }} 38 | files: ./coverage/*.out 39 | codecov_yml_path: tools/.codecov.yaml 40 | verbose: true 41 | 42 | - name: Set up Docker Build 43 | uses: docker/setup-buildx-action@v3 44 | 45 | - name: Login to DockerHub 46 | uses: docker/login-action@v3 47 | with: 48 | registry: ghcr.io 49 | username: ${{ github.actor }} 50 | password: ${{ secrets.GITHUB_TOKEN }} 51 | 52 | - name: Build and push Dockers 53 | run: | 54 | make latest -j $(nproc) 55 | -------------------------------------------------------------------------------- /.github/workflows/check-license.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | name: Check License Header 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | pull_request: 11 | branches: 12 | - main 13 | 14 | jobs: 15 | check-license: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v4 20 | 21 | - name: Check License Header 22 | run: | 23 | CHECK="" 24 | for file in $(grep -rl --exclude-dir={.git,build,**vernemq**,coverage} \ 25 | --exclude=\*.{crt,key,pem,zed,hcl,md,json,csv,mod,sum,tmpl,args} \ 26 | --exclude={CODEOWNERS,LICENSE,MAINTAINERS,enabled_plugins,rabbitmq.conf} \ 27 | .); do 28 | 29 | if ! head -n 5 "$file" | grep -q "Copyright (c) Abstract Machines"; then 30 | CHECK="$CHECK $file" 31 | fi 32 | done 33 | 34 | if [ "$CHECK" ]; then 35 | echo "License header check failed. Fix the following files:" 36 | echo "$CHECK" 37 | exit 1 38 | else 39 | echo "All files have the correct license header!" 40 | fi 41 | -------------------------------------------------------------------------------- /.github/workflows/swagger-ui.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | name: Deploy GitHub Pages 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | swagger-ui: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | 18 | - name: Swagger UI action 19 | id: swagger-ui-action 20 | uses: blokovi/swagger-ui-action@main 21 | with: 22 | dir: "./apidocs/openapi" 23 | pattern: "*.yaml" 24 | debug: "true" 25 | 26 | - name: Deploy to GitHub Pages 27 | uses: peaceiris/actions-gh-pages@v4 28 | with: 29 | github_token: ${{ secrets.GITHUB_TOKEN }} 30 | publish_dir: swagger-ui 31 | cname: docs.api.magistrala.abstractmachines.fr 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # build dirs 5 | build 6 | 7 | # tools 8 | tools/e2e/e2e 9 | tools/mqtt-bench/mqtt-bench 10 | tools/provision/provision 11 | tools/provision/mgconn.toml 12 | 13 | # coverage files 14 | coverage 15 | 16 | # Schemathesis 17 | .hypothesis 18 | 19 | # Ignore Vault data directory as it contains runtime-generated data 20 | docker/addons/vault/data/ 21 | -------------------------------------------------------------------------------- /ADOPTERS.md: -------------------------------------------------------------------------------- 1 | # Adopters 2 | 3 | As Magistrala Community grows, we'd like to keep track of Magistrala adopters to grow the community, contact other users, share experiences and best practices. 4 | 5 | To accomplish this, we created a public ledger. The list of organizations and users who consider themselves as Magistrala adopters and that **publicly/officially** shared information and/or details of their adoption journey(optional). 6 | Where users themselves directly maintain the list. 7 | 8 | ## Adding yourself as an adopter 9 | If you are using Magistrala, please consider adding yourself as an adopter with a brief description of your use case by opening a pull request to this file and adding a section describing your adoption of Magistrala technology. 10 | 11 | **Please send PRs to add or remove organizations/users** 12 | 13 | ### Format 14 | 15 | ``` 16 | N: Name of user (company or individual) 17 | D: Short Use Case Description (optional) 18 | L: Link with further information (optional) 19 | T: Type of adaptation: Evaluation, Core Technology, Production Usage (optional) 20 | ``` 21 | 22 | ## Requirements 23 | * You must represent the user or organization listed. Do NOT add entries on behalf of other organizations or individuals. 24 | Pull request commit must be [signed](https://docs.github.com/en/github/authenticating-to-github/signing-commits) and auto-checked with [ Developer Certificate of Origin (DCO)](https://probot.github.io/apps/dco/) 25 | * There is no minimum requirement or adaptation size, but we request to list permanent deployments only, i.e., no demo or trial deployments. Commercial or production use is not required. A well-done home lab setup can be equally impressive as a large-scale commercial deployment. 26 | 27 | 28 | **The list of organizations/users that have publicly shared the usage of Magistrala:** 29 | 30 | **Note**: Several other organizations/users couldn't publicly share their usage details but are active project contributors and Magistrala Community members. 31 | 32 | 33 | ## Adopters list (alphabetical) 34 | 35 | 36 | **Note:** The list is maintained by the users themselves. If you find yourself on this list, and you think it's inappropriate. Please contact [project maintainers](https://github.com/absmach/magistrala/blob/main/MAINTAINERS) and you will be permanently removed from the list. 37 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | # Magistrala follows the timeless, highly efficient and totally unfair system 2 | # known as [Benevolent dictator for 3 | # life](https://en.wikipedia.org/wiki/Benevolent_Dictator_for_Life), with 4 | # Drasko DRASKOVIC in the role of BDFL. 5 | 6 | [bdfl] 7 | 8 | [[drasko]] 9 | Name = "Drasko Draskovic" 10 | Email = "draasko.draskovic@abstractmachines.fr" 11 | GitHub = "drasko" 12 | 13 | # However, this role serves only in dead-lock events, or in a special and very rare cases 14 | # when BDFL completely disagrees with the decisions made. 15 | # In the normal flow of events, decisions on the project design are made through discussions, 16 | # most often on the Pull Requests. 17 | # 18 | # Maintainers have the special role in the project in managing and accepting PRs, 19 | # overall leading the project and making design decisions on the maintained subsystems. 20 | # 21 | # A reference list of all maintainers of the Magistrala project. 22 | 23 | # ADD YOURSELF HERE IN ALPHABETICAL ORDER 24 | 25 | [maintainers] 26 | 27 | [[dusan]] 28 | Name = "Dusan Borovcanin" 29 | Email = "dusan.borovcanin@abstractmachines.fr" 30 | GitHub = "dborovcanin" 31 | -------------------------------------------------------------------------------- /alarms/api/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package api contains API-related concerns: endpoint definitions, middlewares 5 | // and all resource representations. 6 | package api 7 | -------------------------------------------------------------------------------- /alarms/api/requests.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package api 5 | 6 | import ( 7 | "errors" 8 | 9 | "github.com/absmach/magistrala/alarms" 10 | api "github.com/absmach/supermq/api/http" 11 | apiutil "github.com/absmach/supermq/api/http/util" 12 | ) 13 | 14 | type alarmReq struct { 15 | alarms.Alarm `json:",inline"` 16 | } 17 | 18 | func (req alarmReq) validate() error { 19 | if req.Alarm.ID == "" { 20 | return errors.New("missing alarm id") 21 | } 22 | 23 | return nil 24 | } 25 | 26 | type listAlarmsReq struct { 27 | alarms.PageMetadata 28 | } 29 | 30 | func (req listAlarmsReq) validate() error { 31 | if req.Limit > api.MaxLimitSize || req.Limit < 1 { 32 | return apiutil.ErrLimitSize 33 | } 34 | 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /alarms/api/responses.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package api 5 | 6 | import ( 7 | "fmt" 8 | "net/http" 9 | 10 | "github.com/absmach/magistrala/alarms" 11 | "github.com/absmach/supermq" 12 | ) 13 | 14 | var ( 15 | _ supermq.Response = (*alarmRes)(nil) 16 | _ supermq.Response = (*alarmsPageRes)(nil) 17 | ) 18 | 19 | type alarmRes struct { 20 | alarms.Alarm `json:",inline"` 21 | created bool 22 | deleted bool 23 | } 24 | 25 | func (res alarmRes) Headers() map[string]string { 26 | switch { 27 | case res.created: 28 | return map[string]string{ 29 | "Location": fmt.Sprintf("/%s/alarms/%s", res.DomainID, res.ID), 30 | } 31 | default: 32 | return map[string]string{} 33 | } 34 | } 35 | 36 | func (res alarmRes) Code() int { 37 | switch { 38 | case res.created: 39 | return http.StatusCreated 40 | case res.deleted: 41 | return http.StatusNoContent 42 | default: 43 | return http.StatusOK 44 | } 45 | } 46 | 47 | func (res alarmRes) Empty() bool { 48 | switch { 49 | case res.deleted: 50 | return true 51 | default: 52 | return false 53 | } 54 | } 55 | 56 | type alarmsPageRes struct { 57 | alarms.AlarmsPage `json:",inline"` 58 | } 59 | 60 | func (res alarmsPageRes) Headers() map[string]string { 61 | return map[string]string{} 62 | } 63 | 64 | func (res alarmsPageRes) Code() int { 65 | return http.StatusOK 66 | } 67 | 68 | func (res alarmsPageRes) Empty() bool { 69 | return false 70 | } 71 | -------------------------------------------------------------------------------- /alarms/brokers/brokers_nats.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build !rabbitmq 5 | // +build !rabbitmq 6 | 7 | package brokers 8 | 9 | import ( 10 | "context" 11 | "log/slog" 12 | "time" 13 | 14 | "github.com/absmach/supermq/pkg/messaging" 15 | broker "github.com/absmach/supermq/pkg/messaging/nats" 16 | "github.com/nats-io/nats.go/jetstream" 17 | ) 18 | 19 | const ( 20 | AllTopic = "alarms.>" 21 | 22 | prefix = "alarms" 23 | ) 24 | 25 | var cfg = jetstream.StreamConfig{ 26 | Name: "alarms", 27 | Description: "SuperMQ stream alarms", 28 | Subjects: []string{"alarms.>"}, 29 | Retention: jetstream.LimitsPolicy, 30 | MaxMsgsPerSubject: 1e6, 31 | MaxAge: time.Hour * 24, 32 | MaxMsgSize: 1024 * 1024, 33 | Discard: jetstream.DiscardOld, 34 | Storage: jetstream.FileStorage, 35 | } 36 | 37 | func NewPubSub(ctx context.Context, url string, logger *slog.Logger) (messaging.PubSub, error) { 38 | pb, err := broker.NewPubSub(ctx, url, logger, broker.Prefix(prefix), broker.JSStreamConfig(cfg)) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | return pb, nil 44 | } 45 | 46 | func NewPublisher(ctx context.Context, url string) (messaging.Publisher, error) { 47 | pb, err := broker.NewPublisher(ctx, url, broker.Prefix(prefix), broker.JSStreamConfig(cfg)) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | return pb, nil 53 | } 54 | -------------------------------------------------------------------------------- /alarms/brokers/brokers_rabbitmq.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build rabbitmq 5 | // +build rabbitmq 6 | 7 | package brokers 8 | 9 | import ( 10 | "context" 11 | "log/slog" 12 | 13 | "github.com/absmach/supermq/pkg/messaging" 14 | broker "github.com/absmach/supermq/pkg/messaging/rabbitmq" 15 | ) 16 | 17 | const ( 18 | AllTopic = "alarms.#" 19 | 20 | exchangeName = "alarms" 21 | prefix = "alarms" 22 | ) 23 | 24 | func NewPubSub(_ context.Context, url string, logger *slog.Logger) (messaging.PubSub, error) { 25 | pb, err := broker.NewPubSub(url, logger, broker.Prefix(prefix), broker.Exchange(exchangeName)) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | return pb, nil 31 | } 32 | 33 | func NewPublisher(_ context.Context, url string) (messaging.Publisher, error) { 34 | pb, err := broker.NewPublisher(url, broker.Prefix(prefix), broker.Exchange(exchangeName)) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | return pb, nil 40 | } 41 | -------------------------------------------------------------------------------- /alarms/consumer/consumer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package consumer 5 | 6 | import ( 7 | "bytes" 8 | "context" 9 | "encoding/gob" 10 | "log/slog" 11 | "time" 12 | 13 | "github.com/absmach/magistrala/alarms" 14 | "github.com/absmach/supermq/pkg/errors" 15 | "github.com/absmach/supermq/pkg/messaging" 16 | ) 17 | 18 | type handler struct { 19 | svc alarms.Service 20 | logger *slog.Logger 21 | } 22 | 23 | func Newhandler(svc alarms.Service, logger *slog.Logger) messaging.MessageHandler { 24 | return &handler{svc: svc, logger: logger} 25 | } 26 | 27 | func (h handler) Handle(msg *messaging.Message) (err error) { 28 | if msg == nil { 29 | return errors.New("message is empty") 30 | } 31 | if msg.GetPayload() == nil { 32 | return errors.New("message payload is empty") 33 | } 34 | 35 | var alarm alarms.Alarm 36 | if err := gob.NewDecoder(bytes.NewReader(msg.GetPayload())).Decode(&alarm); err != nil { 37 | return err 38 | } 39 | alarm.DomainID = msg.GetDomain() 40 | alarm.ChannelID = msg.GetChannel() 41 | alarm.ClientID = msg.GetPublisher() 42 | alarm.Subtopic = msg.GetSubtopic() 43 | alarm.CreatedAt = time.Unix(0, int64(msg.GetCreated())) 44 | 45 | if err := alarm.Validate(); err != nil { 46 | return err 47 | } 48 | 49 | return h.svc.CreateAlarm(context.Background(), alarm) 50 | } 51 | 52 | func (h handler) Cancel() error { 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /alarms/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package alarms contains domain concept definitions needed to support 5 | // Alarms service feature, i.e. create, read, update, and delete alarms. 6 | package alarms 7 | -------------------------------------------------------------------------------- /alarms/middleware/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package middleware provides middleware for the alarms service. 5 | // This is logging, metrics, and tracing middleware. 6 | package middleware 7 | -------------------------------------------------------------------------------- /alarms/postgres/init.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package postgres 5 | 6 | import ( 7 | _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access 8 | migrate "github.com/rubenv/sql-migrate" 9 | ) 10 | 11 | // Migration of Users service. 12 | func Migration() *migrate.MemoryMigrationSource { 13 | return &migrate.MemoryMigrationSource{ 14 | Migrations: []*migrate.Migration{ 15 | { 16 | Id: "alarms_01", 17 | // VARCHAR(36) for columns with IDs as UUIDS have a maximum of 36 characters 18 | Up: []string{ 19 | `CREATE TABLE IF NOT EXISTS alarms ( 20 | id VARCHAR(36) PRIMARY KEY, 21 | rule_id VARCHAR(36) NOT NULL CHECK (length(rule_id) > 0), 22 | domain_id VARCHAR(36) NOT NULL, 23 | channel_id VARCHAR(36) NOT NULL, 24 | subtopic TEXT NOT NULL, 25 | client_id VARCHAR(36) NOT NULL, 26 | measurement TEXT NOT NULL, 27 | value TEXT NOT NULL, 28 | unit TEXT NOT NULL, 29 | threshold TEXT NOT NULL, 30 | cause TEXT NOT NULL, 31 | status SMALLINT NOT NULL DEFAULT 0 CHECK (status >= 0), 32 | severity SMALLINT NOT NULL DEFAULT 0 CHECK (severity >= 0), 33 | assignee_id VARCHAR(36), 34 | created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, 35 | updated_at TIMESTAMPTZ NULL, 36 | updated_by VARCHAR(36) NULL, 37 | assigned_at TIMESTAMPTZ NULL, 38 | assigned_by VARCHAR(36) NULL, 39 | acknowledged_at TIMESTAMPTZ NULL, 40 | acknowledged_by VARCHAR(36) NULL, 41 | resolved_at TIMESTAMPTZ NULL, 42 | resolved_by VARCHAR(36) NULL, 43 | metadata JSONB 44 | );`, 45 | "CREATE INDEX IF NOT EXISTS idx_alarms_state ON alarms (domain_id, rule_id, channel_id, subtopic, client_id, measurement, created_at DESC);", 46 | }, 47 | Down: []string{ 48 | `DROP TABLE IF EXISTS alarms`, 49 | }, 50 | }, 51 | }, 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /alarms/postgres/setup_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package postgres_test 5 | 6 | import ( 7 | "database/sql" 8 | "fmt" 9 | "log" 10 | "os" 11 | "testing" 12 | "time" 13 | 14 | apostgres "github.com/absmach/magistrala/alarms/postgres" 15 | "github.com/absmach/supermq/pkg/postgres" 16 | "github.com/jmoiron/sqlx" 17 | dockertest "github.com/ory/dockertest/v3" 18 | "github.com/ory/dockertest/v3/docker" 19 | "go.opentelemetry.io/otel" 20 | ) 21 | 22 | var ( 23 | db *sqlx.DB 24 | database postgres.Database 25 | tracer = otel.Tracer("repo_tests") 26 | ) 27 | 28 | func TestMain(m *testing.M) { 29 | pool, err := dockertest.NewPool("") 30 | if err != nil { 31 | log.Fatalf("Could not connect to docker: %s", err) 32 | } 33 | 34 | container, err := pool.RunWithOptions(&dockertest.RunOptions{ 35 | Repository: "postgres", 36 | Tag: "16.2-alpine", 37 | Env: []string{ 38 | "POSTGRES_USER=test", 39 | "POSTGRES_PASSWORD=test", 40 | "POSTGRES_DB=test", 41 | "listen_addresses = '*'", 42 | }, 43 | }, func(config *docker.HostConfig) { 44 | config.AutoRemove = true 45 | config.RestartPolicy = docker.RestartPolicy{Name: "no"} 46 | }) 47 | if err != nil { 48 | log.Fatalf("Could not start container: %s", err) 49 | } 50 | 51 | port := container.GetPort("5432/tcp") 52 | 53 | // exponential backoff-retry, because the application in the container might not be ready to accept connections yet 54 | pool.MaxWait = 120 * time.Second 55 | if err := pool.Retry(func() error { 56 | url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port) 57 | db, err := sql.Open("pgx", url) 58 | if err != nil { 59 | return err 60 | } 61 | return db.Ping() 62 | }); err != nil { 63 | log.Fatalf("Could not connect to docker: %s", err) 64 | } 65 | 66 | dbConfig := postgres.Config{ 67 | Host: "localhost", 68 | Port: port, 69 | User: "test", 70 | Pass: "test", 71 | Name: "test", 72 | SSLMode: "disable", 73 | SSLCert: "", 74 | SSLKey: "", 75 | SSLRootCert: "", 76 | } 77 | 78 | if db, err = postgres.Setup(dbConfig, *apostgres.Migration()); err != nil { 79 | log.Fatalf("Could not setup test DB connection: %s", err) 80 | } 81 | 82 | database = postgres.NewDatabase(db, dbConfig, tracer) 83 | 84 | code := m.Run() 85 | 86 | // Defers will not be run when using os.Exit 87 | db.Close() 88 | if err := pool.Purge(container); err != nil { 89 | log.Fatalf("Could not purge container: %s", err) 90 | } 91 | 92 | os.Exit(code) 93 | } 94 | -------------------------------------------------------------------------------- /alarms/service.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package alarms 5 | 6 | import ( 7 | "context" 8 | "time" 9 | 10 | "github.com/absmach/supermq" 11 | "github.com/absmach/supermq/pkg/authn" 12 | repoerr "github.com/absmach/supermq/pkg/errors/repository" 13 | ) 14 | 15 | type service struct { 16 | idp supermq.IDProvider 17 | repo Repository 18 | } 19 | 20 | var _ Service = (*service)(nil) 21 | 22 | func NewService(idp supermq.IDProvider, repo Repository) Service { 23 | return &service{ 24 | idp: idp, 25 | repo: repo, 26 | } 27 | } 28 | 29 | func (s *service) CreateAlarm(ctx context.Context, alarm Alarm) error { 30 | id, err := s.idp.ID() 31 | if err != nil { 32 | return err 33 | } 34 | alarm.ID = id 35 | if alarm.CreatedAt.IsZero() { 36 | alarm.CreatedAt = time.Now() 37 | } 38 | 39 | if err := alarm.Validate(); err != nil { 40 | return err 41 | } 42 | 43 | if _, err = s.repo.CreateAlarm(ctx, alarm); err != nil && err != repoerr.ErrNotFound { 44 | return err 45 | } 46 | return nil 47 | } 48 | 49 | func (s *service) ViewAlarm(ctx context.Context, session authn.Session, alarmID string) (Alarm, error) { 50 | return s.repo.ViewAlarm(ctx, alarmID, session.DomainID) 51 | } 52 | 53 | func (s *service) ListAlarms(ctx context.Context, session authn.Session, pm PageMetadata) (AlarmsPage, error) { 54 | return s.repo.ListAlarms(ctx, pm) 55 | } 56 | 57 | func (s *service) DeleteAlarm(ctx context.Context, session authn.Session, alarmID string) error { 58 | return s.repo.DeleteAlarm(ctx, alarmID) 59 | } 60 | 61 | func (s *service) UpdateAlarm(ctx context.Context, session authn.Session, alarm Alarm) (Alarm, error) { 62 | alarm.UpdatedAt = time.Now() 63 | alarm.UpdatedBy = session.UserID 64 | 65 | return s.repo.UpdateAlarm(ctx, alarm) 66 | } 67 | -------------------------------------------------------------------------------- /alarms/status.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package alarms 5 | 6 | import ( 7 | "encoding/json" 8 | "strings" 9 | 10 | svcerr "github.com/absmach/supermq/pkg/errors/service" 11 | ) 12 | 13 | type Status uint8 14 | 15 | const ( 16 | ActiveStatus Status = iota 17 | ClearedStatus 18 | 19 | // AllStatus is used for querying purposes to list alarms irrespective 20 | // of their status. It is never stored in the database as the actual 21 | // Alarm status and should always be the largest value in this enumeration. 22 | AllStatus 23 | ) 24 | 25 | const ( 26 | Active = "active" 27 | Cleared = "cleared" 28 | Unknown = "unknown" 29 | All = "all" 30 | ) 31 | 32 | // String converts alarm status to string literal. 33 | func (s Status) String() string { 34 | switch s { 35 | case ActiveStatus: 36 | return Active 37 | case ClearedStatus: 38 | return Cleared 39 | default: 40 | return Unknown 41 | } 42 | } 43 | 44 | // ToStatus converts string value to a valid Alarm status. 45 | func ToStatus(status string) (Status, error) { 46 | switch strings.ToLower(status) { 47 | case Active: 48 | return ActiveStatus, nil 49 | case Cleared: 50 | return ClearedStatus, nil 51 | case All: 52 | return AllStatus, nil 53 | default: 54 | return Status(0), svcerr.ErrInvalidStatus 55 | } 56 | } 57 | 58 | // Custom Marshaller for Alarm. 59 | func (s Status) MarshalJSON() ([]byte, error) { 60 | return json.Marshal(s.String()) 61 | } 62 | 63 | // Custom Unmarshaler for Alarm. 64 | func (s *Status) UnmarshalJSON(data []byte) error { 65 | str := strings.Trim(string(data), "\"") 66 | val, err := ToStatus(str) 67 | *s = val 68 | 69 | return err 70 | } 71 | -------------------------------------------------------------------------------- /api/common.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package api 5 | 6 | import ( 7 | "context" 8 | "encoding/json" 9 | "net/http" 10 | 11 | "github.com/absmach/magistrala/bootstrap" 12 | api "github.com/absmach/supermq/api/http" 13 | apiutil "github.com/absmach/supermq/api/http/util" 14 | "github.com/absmach/supermq/pkg/errors" 15 | ) 16 | 17 | // EncodeError encodes an error response. 18 | func EncodeError(ctx context.Context, err error, w http.ResponseWriter) { 19 | var wrapper error 20 | if errors.Contains(err, apiutil.ErrValidation) { 21 | wrapper, err = errors.Unwrap(err) 22 | } 23 | 24 | w.Header().Set("Content-Type", api.ContentType) 25 | 26 | status, nerr := toStatus(err) 27 | if nerr != nil { 28 | err = unwrap(err) 29 | w.WriteHeader(status) 30 | encodeErrorMessage(err, wrapper, w) 31 | return 32 | } 33 | 34 | if wrapper != nil { 35 | err = errors.Wrap(wrapper, err) 36 | } 37 | api.EncodeError(ctx, err, w) 38 | } 39 | 40 | func toStatus(err error) (int, error) { 41 | switch { 42 | case errors.Contains(err, bootstrap.ErrExternalKey), 43 | errors.Contains(err, bootstrap.ErrExternalKeySecure): 44 | return http.StatusForbidden, err 45 | 46 | case errors.Contains(err, bootstrap.ErrBootstrapState), 47 | errors.Contains(err, bootstrap.ErrAddBootstrap): 48 | return http.StatusBadRequest, err 49 | 50 | case errors.Contains(err, bootstrap.ErrBootstrap): 51 | return http.StatusNotFound, err 52 | 53 | default: 54 | return 0, nil 55 | } 56 | } 57 | 58 | func encodeErrorMessage(err, wrapper error, w http.ResponseWriter) { 59 | if wrapper != nil { 60 | err = errors.Wrap(wrapper, err) 61 | } 62 | if errorVal, ok := err.(errors.Error); ok { 63 | if err := json.NewEncoder(w).Encode(errorVal); err != nil { 64 | w.WriteHeader(http.StatusInternalServerError) 65 | } 66 | } 67 | } 68 | 69 | func unwrap(err error) error { 70 | wrapper, err := errors.Unwrap(err) 71 | if wrapper != nil { 72 | return wrapper 73 | } 74 | return err 75 | } 76 | -------------------------------------------------------------------------------- /apidocs/openapi/README.md: -------------------------------------------------------------------------------- 1 | # Magistrala OpenAPI Specification 2 | 3 | This folder contains an OpenAPI specifications for Magistrala API. 4 | 5 | View specification in Swagger UI at [docs.api.magistrala.abstractmachines.fr](https://docs.api.magistrala.abstractmachines.fr) -------------------------------------------------------------------------------- /apidocs/openapi/schemas/health_info.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | type: object 5 | properties: 6 | status: 7 | type: string 8 | description: Service status. 9 | enum: 10 | - pass 11 | version: 12 | type: string 13 | description: Service version. 14 | example: v0.14.0 15 | commit: 16 | type: string 17 | description: Service commit hash. 18 | example: 73362210dd2e04e389eaddb802cab3fe03976593 19 | description: 20 | type: string 21 | description: Service description. 22 | example: <service_name> service 23 | build_time: 24 | type: string 25 | description: Service build time. 26 | example: 2024-02-01_12:18:15 27 | instance_id: 28 | type: string 29 | description: Service instance ID. 30 | example: 8edbf8af-7db7-4218-bb4f-a8a929ff5266 31 | -------------------------------------------------------------------------------- /bootstrap/api/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package api contains implementation of bootstrap service HTTP API. 5 | package api 6 | -------------------------------------------------------------------------------- /bootstrap/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package bootstrap contains the domain concept definitions needed to support 5 | // SuperMQ bootstrap service functionality. 6 | package bootstrap 7 | -------------------------------------------------------------------------------- /bootstrap/events/consumer/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package consumer contains events consumer for events 5 | // published by Bootstrap service. 6 | package consumer 7 | -------------------------------------------------------------------------------- /bootstrap/events/consumer/events.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package consumer 5 | 6 | import "time" 7 | 8 | type removeEvent struct { 9 | id string 10 | } 11 | 12 | type updateChannelEvent struct { 13 | id string 14 | name string 15 | metadata map[string]interface{} 16 | updatedAt time.Time 17 | updatedBy string 18 | } 19 | 20 | // Connection event is either connect or disconnect event. 21 | type connectionEvent struct { 22 | clientIDs []string 23 | channelID string 24 | } 25 | -------------------------------------------------------------------------------- /bootstrap/events/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package events provides the domain concept definitions needed to support 5 | // bootstrap events functionality. 6 | package events 7 | -------------------------------------------------------------------------------- /bootstrap/events/producer/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package producer contains the domain events needed to support 5 | // event sourcing of Bootstrap service actions. 6 | package producer 7 | -------------------------------------------------------------------------------- /bootstrap/events/producer/setup_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package producer_test 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "log" 10 | "os" 11 | "testing" 12 | 13 | "github.com/ory/dockertest/v3" 14 | "github.com/ory/dockertest/v3/docker" 15 | "github.com/redis/go-redis/v9" 16 | ) 17 | 18 | var ( 19 | redisClient *redis.Client 20 | redisURL string 21 | ) 22 | 23 | func TestMain(m *testing.M) { 24 | pool, err := dockertest.NewPool("") 25 | if err != nil { 26 | log.Fatalf("Could not connect to docker: %s", err) 27 | } 28 | 29 | container, err := pool.RunWithOptions(&dockertest.RunOptions{ 30 | Repository: "redis", 31 | Tag: "7.2.4-alpine", 32 | }, func(config *docker.HostConfig) { 33 | config.AutoRemove = true 34 | config.RestartPolicy = docker.RestartPolicy{Name: "no"} 35 | }) 36 | if err != nil { 37 | log.Fatalf("Could not start container: %s", err) 38 | } 39 | 40 | redisURL = fmt.Sprintf("redis://localhost:%s/0", container.GetPort("6379/tcp")) 41 | opts, err := redis.ParseURL(redisURL) 42 | if err != nil { 43 | log.Fatalf("Could not parse redis URL: %s", err) 44 | } 45 | 46 | if err := pool.Retry(func() error { 47 | redisClient = redis.NewClient(opts) 48 | 49 | return redisClient.Ping(context.Background()).Err() 50 | }); err != nil { 51 | log.Fatalf("Could not connect to docker: %s", err) 52 | } 53 | 54 | code := m.Run() 55 | 56 | if err := pool.Purge(container); err != nil { 57 | log.Fatalf("Could not purge container: %s", err) 58 | } 59 | 60 | os.Exit(code) 61 | } 62 | -------------------------------------------------------------------------------- /bootstrap/postgres/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package postgres contains repository implementations using PostgreSQL as 5 | // the underlying database. 6 | package postgres 7 | -------------------------------------------------------------------------------- /bootstrap/postgres/setup_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package postgres_test 5 | 6 | import ( 7 | "fmt" 8 | "log" 9 | "os" 10 | "testing" 11 | 12 | "github.com/absmach/magistrala/bootstrap/postgres" 13 | smqlog "github.com/absmach/supermq/logger" 14 | pgclient "github.com/absmach/supermq/pkg/postgres" 15 | "github.com/jmoiron/sqlx" 16 | "github.com/ory/dockertest/v3" 17 | "github.com/ory/dockertest/v3/docker" 18 | ) 19 | 20 | var ( 21 | testLog, _ = smqlog.New(os.Stdout, "info") 22 | db *sqlx.DB 23 | ) 24 | 25 | func TestMain(m *testing.M) { 26 | pool, err := dockertest.NewPool("") 27 | if err != nil { 28 | testLog.Error(fmt.Sprintf("Could not connect to docker: %s", err)) 29 | } 30 | 31 | container, err := pool.RunWithOptions(&dockertest.RunOptions{ 32 | Repository: "postgres", 33 | Tag: "16.2-alpine", 34 | Env: []string{ 35 | "POSTGRES_USER=test", 36 | "POSTGRES_PASSWORD=test", 37 | "POSTGRES_DB=test", 38 | "listen_addresses = '*'", 39 | }, 40 | }, func(config *docker.HostConfig) { 41 | config.AutoRemove = true 42 | config.RestartPolicy = docker.RestartPolicy{Name: "no"} 43 | }) 44 | if err != nil { 45 | log.Fatalf("Could not start container: %s", err) 46 | } 47 | 48 | port := container.GetPort("5432/tcp") 49 | 50 | if err := pool.Retry(func() error { 51 | url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port) 52 | db, err = sqlx.Open("pgx", url) 53 | if err != nil { 54 | return err 55 | } 56 | return db.Ping() 57 | }); err != nil { 58 | testLog.Error(fmt.Sprintf("Could not connect to docker: %s", err)) 59 | } 60 | 61 | dbConfig := pgclient.Config{ 62 | Host: "localhost", 63 | Port: port, 64 | User: "test", 65 | Pass: "test", 66 | Name: "test", 67 | SSLMode: "disable", 68 | SSLCert: "", 69 | SSLKey: "", 70 | SSLRootCert: "", 71 | } 72 | 73 | if db, err = pgclient.Setup(dbConfig, *postgres.Migration()); err != nil { 74 | testLog.Error(fmt.Sprintf("Could not setup test DB connection: %s", err)) 75 | } 76 | 77 | code := m.Run() 78 | 79 | // Defers will not be run when using os.Exit 80 | db.Close() 81 | if err := pool.Purge(container); err != nil { 82 | testLog.Error(fmt.Sprintf("Could not purge container: %s", err)) 83 | } 84 | 85 | os.Exit(code) 86 | } 87 | -------------------------------------------------------------------------------- /bootstrap/state.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package bootstrap 5 | 6 | import "strconv" 7 | 8 | const ( 9 | // Inactive Client is created, but not able to exchange messages using SuperMQ. 10 | Inactive State = iota 11 | // Active Client is created, configured, and whitelisted. 12 | Active 13 | ) 14 | 15 | // State represents corresponding SuperMQ Client state. The possible Config States 16 | // as well as description of what that State represents are given in the table: 17 | // | State | What it means | 18 | // |----------+--------------------------------------------------------------------------------| 19 | // | Inactive | Client is created, but isn't able to communicate over SuperMQ | 20 | // | Active | Client is able to communicate using SuperMQ |. 21 | type State int 22 | 23 | // String returns string representation of State. 24 | func (s State) String() string { 25 | return strconv.Itoa(int(s)) 26 | } 27 | -------------------------------------------------------------------------------- /bootstrap/tracing/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package tracing provides tracing instrumentation for SuperMQ Users service. 5 | // 6 | // This package provides tracing middleware for SuperMQ Users service. 7 | // It can be used to trace incoming requests and add tracing capabilities to 8 | // SuperMQ Users service. 9 | // 10 | // For more details about tracing instrumentation for SuperMQ messaging refer 11 | // to the documentation at https://docs.supermq.abstractmachines.fr/tracing/. 12 | package tracing 13 | -------------------------------------------------------------------------------- /cli/commands_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cli_test 5 | 6 | // CRUD and common commands 7 | const ( 8 | createCmd = "create" 9 | updateCmd = "update" 10 | getCmd = "get" 11 | enableCmd = "enable" 12 | disableCmd = "disable" 13 | updCmd = "update" 14 | delCmd = "delete" 15 | rmCmd = "remove" 16 | whitelistCmd = "whitelist" 17 | bootStrapCmd = "bootstrap" 18 | ) 19 | -------------------------------------------------------------------------------- /cli/sdk.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cli 5 | 6 | import mgsdk "github.com/absmach/magistrala/pkg/sdk" 7 | 8 | // Keep SDK handle in global var. 9 | var sdk mgsdk.SDK 10 | 11 | // SetSDK sets supermq SDK instance. 12 | func SetSDK(s mgsdk.SDK) { 13 | sdk = s 14 | } 15 | -------------------------------------------------------------------------------- /cli/setup_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cli_test 5 | 6 | import ( 7 | "bytes" 8 | "testing" 9 | 10 | "github.com/absmach/supermq/cli" 11 | "github.com/spf13/cobra" 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | type outputLog uint8 16 | 17 | const ( 18 | usageLog outputLog = iota 19 | errLog 20 | entityLog 21 | okLog 22 | createLog 23 | revokeLog 24 | ) 25 | 26 | func executeCommand(t *testing.T, root *cobra.Command, args ...string) string { 27 | buffer := new(bytes.Buffer) 28 | root.SetOut(buffer) 29 | root.SetErr(buffer) 30 | root.SetArgs(args) 31 | err := root.Execute() 32 | assert.NoError(t, err, "Error executing command") 33 | return buffer.String() 34 | } 35 | 36 | func setFlags(rootCmd *cobra.Command) *cobra.Command { 37 | // Root Flags 38 | rootCmd.PersistentFlags().BoolVarP( 39 | &cli.RawOutput, 40 | "raw", 41 | "r", 42 | cli.RawOutput, 43 | "Enables raw output mode for easier parsing of output", 44 | ) 45 | 46 | // Client and Channels Flags 47 | rootCmd.PersistentFlags().Uint64VarP( 48 | &cli.Limit, 49 | "limit", 50 | "l", 51 | 10, 52 | "Limit query parameter", 53 | ) 54 | 55 | rootCmd.PersistentFlags().Uint64VarP( 56 | &cli.Offset, 57 | "offset", 58 | "o", 59 | 0, 60 | "Offset query parameter", 61 | ) 62 | 63 | rootCmd.PersistentFlags().StringVarP( 64 | &cli.Name, 65 | "name", 66 | "n", 67 | "", 68 | "Name query parameter", 69 | ) 70 | 71 | rootCmd.PersistentFlags().StringVarP( 72 | &cli.Identity, 73 | "identity", 74 | "I", 75 | "", 76 | "User identity query parameter", 77 | ) 78 | 79 | rootCmd.PersistentFlags().StringVarP( 80 | &cli.Metadata, 81 | "metadata", 82 | "m", 83 | "", 84 | "Metadata query parameter", 85 | ) 86 | 87 | rootCmd.PersistentFlags().StringVarP( 88 | &cli.Status, 89 | "status", 90 | "S", 91 | "", 92 | "User status query parameter", 93 | ) 94 | 95 | rootCmd.PersistentFlags().StringVarP( 96 | &cli.State, 97 | "state", 98 | "z", 99 | "", 100 | "Bootstrap state query parameter", 101 | ) 102 | 103 | rootCmd.PersistentFlags().StringVarP( 104 | &cli.Topic, 105 | "topic", 106 | "T", 107 | "", 108 | "Subscription topic query parameter", 109 | ) 110 | 111 | rootCmd.PersistentFlags().StringVarP( 112 | &cli.Contact, 113 | "contact", 114 | "C", 115 | "", 116 | "Subscription contact query parameter", 117 | ) 118 | 119 | return rootCmd 120 | } 121 | -------------------------------------------------------------------------------- /cli/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cli 5 | 6 | import ( 7 | "encoding/json" 8 | "fmt" 9 | 10 | "github.com/fatih/color" 11 | "github.com/hokaccha/go-prettyjson" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | var ( 16 | // Limit query parameter. 17 | Limit uint64 = 10 18 | // Offset query parameter. 19 | Offset uint64 = 0 20 | // Name query parameter. 21 | Name string = "" 22 | // Identity query parameter. 23 | Identity string = "" 24 | // Metadata query parameter. 25 | Metadata string = "" 26 | // Status query parameter. 27 | Status string = "" 28 | // ConfigPath config path parameter. 29 | ConfigPath string = "" 30 | // State query parameter. 31 | State string = "" 32 | // Topic query parameter. 33 | Topic string = "" 34 | // Contact query parameter. 35 | Contact string = "" 36 | // RawOutput raw output mode. 37 | RawOutput bool = false 38 | // Username query parameter. 39 | Username string = "" 40 | // FirstName query parameter. 41 | FirstName string = "" 42 | // LastName query parameter. 43 | LastName string = "" 44 | ) 45 | 46 | func logJSONCmd(cmd cobra.Command, iList ...interface{}) { 47 | for _, i := range iList { 48 | m, err := json.Marshal(i) 49 | if err != nil { 50 | logErrorCmd(cmd, err) 51 | return 52 | } 53 | 54 | pj, err := prettyjson.Format(m) 55 | if err != nil { 56 | logErrorCmd(cmd, err) 57 | return 58 | } 59 | 60 | fmt.Fprintf(cmd.OutOrStdout(), "\n%s\n\n", string(pj)) 61 | } 62 | } 63 | 64 | func logUsageCmd(cmd cobra.Command, u string) { 65 | fmt.Fprintf(cmd.OutOrStdout(), color.YellowString("\nusage: %s\n\n"), u) 66 | } 67 | 68 | func logErrorCmd(cmd cobra.Command, err error) { 69 | boldRed := color.New(color.FgRed, color.Bold) 70 | boldRed.Fprintf(cmd.ErrOrStderr(), "\nerror: ") 71 | 72 | fmt.Fprintf(cmd.ErrOrStderr(), "%s\n\n", color.RedString(err.Error())) 73 | } 74 | 75 | func logOKCmd(cmd cobra.Command) { 76 | fmt.Fprintf(cmd.OutOrStdout(), "\n%s\n\n", color.BlueString("ok")) 77 | } 78 | 79 | func logCreatedCmd(cmd cobra.Command, e string) { 80 | if RawOutput { 81 | fmt.Fprintln(cmd.OutOrStdout(), e) 82 | } else { 83 | fmt.Fprintf(cmd.OutOrStdout(), color.BlueString("\ncreated: %s\n\n"), e) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | raw_output = "false" 5 | user_token = "" 6 | 7 | [filter] 8 | limit = "10" 9 | offset = "0" 10 | topic = "" 11 | 12 | [remotes] 13 | journal_url = "http://localhost:9021" 14 | bootstrap_url = "http://localhost:9013" 15 | certs_url = "http://localhost:9019" 16 | domains_url = "http://localhost:9003" 17 | host_url = "http://localhost" 18 | http_adapter_url = "http://localhost:8008" 19 | reader_url = "http://localhost:9011" 20 | clients_url = "http://localhost:9006" 21 | channels_url = "http://localhost:9005" 22 | groups_url = "http://localhost:9004" 23 | users_url = "http://localhost:9002" 24 | tls_verification = false 25 | -------------------------------------------------------------------------------- /consumers/README.md: -------------------------------------------------------------------------------- 1 | # Consumers 2 | 3 | Consumers provide an abstraction of various `SuperMQ consumers`. 4 | SuperMQ consumer is a generic service that can handle received messages - consume them. 5 | The message is not necessarily a SuperMQ message - before consuming, SuperMQ message can 6 | be transformed into any valid format that specific consumer can understand. For example, 7 | writers are consumers that can take a SenML or JSON message and store it. 8 | 9 | Consumers are optional services and are treated as plugins. In order to 10 | run consumer services, core services must be up and running. 11 | 12 | For an in-depth explanation of the usage of `consumers`, as well as thorough 13 | understanding of SuperMQ, please check out the [official documentation][doc]. 14 | 15 | For more information about service capabilities and its usage, please check out 16 | the [API documentation](https://docs.api.supermq.abstractmachines.fr/?urls.primaryName=consumers-notifiers-openapi.yaml). 17 | 18 | [doc]: https://docs.supermq.abstractmachines.fr 19 | -------------------------------------------------------------------------------- /consumers/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package consumers contain the domain concept definitions needed to 5 | // support SuperMQ consumer services functionality. 6 | package consumers 7 | -------------------------------------------------------------------------------- /consumers/notifiers/README.md: -------------------------------------------------------------------------------- 1 | # Notifiers service 2 | 3 | Notifiers service provides a service for sending notifications using Notifiers. 4 | Notifiers service can be configured to use different types of Notifiers to send 5 | different types of notifications such as SMS messages, emails, or push notifications. 6 | Service is extensible so that new implementations of Notifiers can be easily added. 7 | Notifiers **are not standalone services** but rather dependencies used by Notifiers service 8 | for sending notifications over specific protocols. 9 | 10 | ## Configuration 11 | 12 | The service is configured using the environment variables. 13 | The environment variables needed for service configuration depend on the underlying Notifier. 14 | An example of the service configuration for SMTP Notifier can be found [in SMTP Notifier documentation](smtp/README.md). 15 | Note that any unset variables will be replaced with their 16 | default values. 17 | 18 | 19 | ## Usage 20 | 21 | Subscriptions service will start consuming messages and sending notifications when a message is received. 22 | 23 | [doc]: https://docs.supermq.abstractmachines.fr 24 | -------------------------------------------------------------------------------- /consumers/notifiers/api/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package api contains API-related concerns: endpoint definitions, middlewares 5 | // and all resource representations. 6 | package api 7 | -------------------------------------------------------------------------------- /consumers/notifiers/api/requests.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package api 5 | 6 | import apiutil "github.com/absmach/supermq/api/http/util" 7 | 8 | type createSubReq struct { 9 | token string 10 | Topic string `json:"topic,omitempty"` 11 | Contact string `json:"contact,omitempty"` 12 | } 13 | 14 | func (req createSubReq) validate() error { 15 | if req.token == "" { 16 | return apiutil.ErrBearerToken 17 | } 18 | if req.Topic == "" { 19 | return apiutil.ErrInvalidTopic 20 | } 21 | if req.Contact == "" { 22 | return apiutil.ErrInvalidContact 23 | } 24 | return nil 25 | } 26 | 27 | type subReq struct { 28 | token string 29 | id string 30 | } 31 | 32 | func (req subReq) validate() error { 33 | if req.token == "" { 34 | return apiutil.ErrBearerToken 35 | } 36 | if req.id == "" { 37 | return apiutil.ErrMissingID 38 | } 39 | return nil 40 | } 41 | 42 | type listSubsReq struct { 43 | token string 44 | topic string 45 | contact string 46 | offset uint 47 | limit uint 48 | } 49 | 50 | func (req listSubsReq) validate() error { 51 | if req.token == "" { 52 | return apiutil.ErrBearerToken 53 | } 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /consumers/notifiers/api/responses.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package api 5 | 6 | import ( 7 | "fmt" 8 | "net/http" 9 | 10 | "github.com/absmach/supermq" 11 | ) 12 | 13 | var ( 14 | _ supermq.Response = (*createSubRes)(nil) 15 | _ supermq.Response = (*viewSubRes)(nil) 16 | _ supermq.Response = (*listSubsRes)(nil) 17 | _ supermq.Response = (*removeSubRes)(nil) 18 | ) 19 | 20 | type createSubRes struct { 21 | ID string 22 | } 23 | 24 | func (res createSubRes) Code() int { 25 | return http.StatusCreated 26 | } 27 | 28 | func (res createSubRes) Headers() map[string]string { 29 | return map[string]string{ 30 | "Location": fmt.Sprintf("/subscriptions/%s", res.ID), 31 | } 32 | } 33 | 34 | func (res createSubRes) Empty() bool { 35 | return true 36 | } 37 | 38 | type viewSubRes struct { 39 | ID string `json:"id"` 40 | OwnerID string `json:"owner_id"` 41 | Contact string `json:"contact"` 42 | Topic string `json:"topic"` 43 | } 44 | 45 | func (res viewSubRes) Code() int { 46 | return http.StatusOK 47 | } 48 | 49 | func (res viewSubRes) Headers() map[string]string { 50 | return map[string]string{} 51 | } 52 | 53 | func (res viewSubRes) Empty() bool { 54 | return false 55 | } 56 | 57 | type listSubsRes struct { 58 | Offset uint `json:"offset"` 59 | Limit int `json:"limit"` 60 | Total uint `json:"total,omitempty"` 61 | Subscriptions []viewSubRes `json:"subscriptions,omitempty"` 62 | } 63 | 64 | func (res listSubsRes) Code() int { 65 | return http.StatusOK 66 | } 67 | 68 | func (res listSubsRes) Headers() map[string]string { 69 | return map[string]string{} 70 | } 71 | 72 | func (res listSubsRes) Empty() bool { 73 | return false 74 | } 75 | 76 | type removeSubRes struct{} 77 | 78 | func (res removeSubRes) Code() int { 79 | return http.StatusNoContent 80 | } 81 | 82 | func (res removeSubRes) Headers() map[string]string { 83 | return map[string]string{} 84 | } 85 | 86 | func (res removeSubRes) Empty() bool { 87 | return true 88 | } 89 | -------------------------------------------------------------------------------- /consumers/notifiers/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package notifiers contain the domain concept definitions needed to 5 | // support SuperMQ notifications functionality. 6 | package notifiers 7 | -------------------------------------------------------------------------------- /consumers/notifiers/postgres/database.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package postgres 5 | 6 | import ( 7 | "context" 8 | "database/sql" 9 | "fmt" 10 | 11 | "github.com/jmoiron/sqlx" 12 | "go.opentelemetry.io/otel/attribute" 13 | "go.opentelemetry.io/otel/trace" 14 | ) 15 | 16 | var _ Database = (*database)(nil) 17 | 18 | type database struct { 19 | db *sqlx.DB 20 | tracer trace.Tracer 21 | } 22 | 23 | // Database provides a database interface. 24 | type Database interface { 25 | NamedExecContext(context.Context, string, interface{}) (sql.Result, error) 26 | QueryRowxContext(context.Context, string, ...interface{}) *sqlx.Row 27 | NamedQueryContext(context.Context, string, interface{}) (*sqlx.Rows, error) 28 | GetContext(context.Context, interface{}, string, ...interface{}) error 29 | } 30 | 31 | // NewDatabase creates a SubscriptionsDatabase instance. 32 | func NewDatabase(db *sqlx.DB, tracer trace.Tracer) Database { 33 | return &database{ 34 | db: db, 35 | tracer: tracer, 36 | } 37 | } 38 | 39 | func (dm database) NamedExecContext(ctx context.Context, query string, args interface{}) (sql.Result, error) { 40 | ctx, span := dm.addSpanTags(ctx, "NamedExecContext", query) 41 | defer span.End() 42 | return dm.db.NamedExecContext(ctx, query, args) 43 | } 44 | 45 | func (dm database) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *sqlx.Row { 46 | ctx, span := dm.addSpanTags(ctx, "QueryRowxContext", query) 47 | defer span.End() 48 | return dm.db.QueryRowxContext(ctx, query, args...) 49 | } 50 | 51 | func (dm database) NamedQueryContext(ctx context.Context, query string, args interface{}) (*sqlx.Rows, error) { 52 | ctx, span := dm.addSpanTags(ctx, "NamedQueryContext", query) 53 | defer span.End() 54 | return dm.db.NamedQueryContext(ctx, query, args) 55 | } 56 | 57 | func (dm database) GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error { 58 | ctx, span := dm.addSpanTags(ctx, "GetContext", query) 59 | defer span.End() 60 | return dm.db.GetContext(ctx, dest, query, args...) 61 | } 62 | 63 | func (dm database) addSpanTags(ctx context.Context, method, query string) (context.Context, trace.Span) { 64 | ctx, span := dm.tracer.Start(ctx, 65 | fmt.Sprintf("sql_%s", method), 66 | trace.WithAttributes( 67 | attribute.String("sql.statement", query), 68 | attribute.String("span.kind", "client"), 69 | attribute.String("peer.service", "postgres"), 70 | attribute.String("db.type", "sql"), 71 | ), 72 | ) 73 | return ctx, span 74 | } 75 | -------------------------------------------------------------------------------- /consumers/notifiers/postgres/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package postgres contains repository implementations using PostgreSQL as 5 | // the underlying database. 6 | package postgres 7 | -------------------------------------------------------------------------------- /consumers/notifiers/postgres/init.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package postgres 5 | 6 | import migrate "github.com/rubenv/sql-migrate" 7 | 8 | func Migration() *migrate.MemoryMigrationSource { 9 | return &migrate.MemoryMigrationSource{ 10 | Migrations: []*migrate.Migration{ 11 | { 12 | Id: "subscriptions_1", 13 | Up: []string{ 14 | `CREATE TABLE IF NOT EXISTS subscriptions ( 15 | id VARCHAR(254) PRIMARY KEY, 16 | owner_id VARCHAR(254) NOT NULL, 17 | contact VARCHAR(254), 18 | topic TEXT, 19 | UNIQUE(topic, contact) 20 | )`, 21 | }, 22 | Down: []string{ 23 | "DROP TABLE IF EXISTS subscriptions", 24 | }, 25 | }, 26 | }, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /consumers/notifiers/postgres/setup_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package postgres_test contains tests for PostgreSQL repository 5 | // implementations. 6 | package postgres_test 7 | 8 | import ( 9 | "fmt" 10 | "log" 11 | "os" 12 | "testing" 13 | 14 | "github.com/absmach/magistrala/consumers/notifiers/postgres" 15 | pgclient "github.com/absmach/supermq/pkg/postgres" 16 | "github.com/absmach/supermq/pkg/ulid" 17 | _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access 18 | "github.com/jmoiron/sqlx" 19 | "github.com/ory/dockertest/v3" 20 | "github.com/ory/dockertest/v3/docker" 21 | ) 22 | 23 | var ( 24 | idProvider = ulid.New() 25 | db *sqlx.DB 26 | ) 27 | 28 | func TestMain(m *testing.M) { 29 | pool, err := dockertest.NewPool("") 30 | if err != nil { 31 | log.Fatalf("Could not connect to docker: %s", err) 32 | } 33 | 34 | container, err := pool.RunWithOptions(&dockertest.RunOptions{ 35 | Repository: "postgres", 36 | Tag: "16.2-alpine", 37 | Env: []string{ 38 | "POSTGRES_USER=test", 39 | "POSTGRES_PASSWORD=test", 40 | "POSTGRES_DB=test", 41 | "listen_addresses = '*'", 42 | }, 43 | }, func(config *docker.HostConfig) { 44 | config.AutoRemove = true 45 | config.RestartPolicy = docker.RestartPolicy{Name: "no"} 46 | }) 47 | if err != nil { 48 | log.Fatalf("Could not start container: %s", err) 49 | } 50 | 51 | port := container.GetPort("5432/tcp") 52 | 53 | url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port) 54 | if err := pool.Retry(func() error { 55 | db, err = sqlx.Open("pgx", url) 56 | if err != nil { 57 | return err 58 | } 59 | return db.Ping() 60 | }); err != nil { 61 | log.Fatalf("Could not connect to docker: %s", err) 62 | } 63 | 64 | dbConfig := pgclient.Config{ 65 | Host: "localhost", 66 | Port: port, 67 | User: "test", 68 | Pass: "test", 69 | Name: "test", 70 | SSLMode: "disable", 71 | SSLCert: "", 72 | SSLKey: "", 73 | SSLRootCert: "", 74 | } 75 | 76 | if db, err = pgclient.Setup(dbConfig, *postgres.Migration()); err != nil { 77 | log.Fatalf("Could not setup test DB connection: %s", err) 78 | } 79 | 80 | code := m.Run() 81 | 82 | // Defers will not be run when using os.Exit 83 | db.Close() 84 | if err := pool.Purge(container); err != nil { 85 | log.Fatalf("Could not purge container: %s", err) 86 | } 87 | 88 | os.Exit(code) 89 | } 90 | -------------------------------------------------------------------------------- /consumers/notifiers/smpp/config.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package smpp 5 | 6 | import ( 7 | "crypto/tls" 8 | ) 9 | 10 | // Config represents SMPP transmitter configuration. 11 | type Config struct { 12 | Address string `env:"MG_SMPP_ADDRESS" envDefault:""` 13 | Username string `env:"MG_SMPP_USERNAME" envDefault:""` 14 | Password string `env:"MG_SMPP_PASSWORD" envDefault:""` 15 | SystemType string `env:"MG_SMPP_SYSTEM_TYPE" envDefault:""` 16 | SourceAddrTON uint8 `env:"MG_SMPP_SRC_ADDR_TON" envDefault:"0"` 17 | SourceAddrNPI uint8 `env:"MG_SMPP_DST_ADDR_TON" envDefault:"0"` 18 | DestAddrTON uint8 `env:"MG_SMPP_SRC_ADDR_NPI" envDefault:"0"` 19 | DestAddrNPI uint8 `env:"MG_SMPP_DST_ADDR_NPI" envDefault:"0"` 20 | TLS *tls.Config 21 | } 22 | -------------------------------------------------------------------------------- /consumers/notifiers/smpp/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package smpp contains the domain concept definitions needed to 5 | // support Magistrala SMS notifications. 6 | package smpp 7 | -------------------------------------------------------------------------------- /consumers/notifiers/smpp/notifier.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package smpp 5 | 6 | import ( 7 | "time" 8 | 9 | "github.com/absmach/supermq/consumers" 10 | "github.com/absmach/supermq/pkg/messaging" 11 | "github.com/absmach/supermq/pkg/transformers" 12 | "github.com/absmach/supermq/pkg/transformers/json" 13 | "github.com/fiorix/go-smpp/smpp" 14 | "github.com/fiorix/go-smpp/smpp/pdu/pdufield" 15 | "github.com/fiorix/go-smpp/smpp/pdu/pdutext" 16 | ) 17 | 18 | var _ consumers.Notifier = (*notifier)(nil) 19 | 20 | type notifier struct { 21 | transmitter *smpp.Transmitter 22 | transformer transformers.Transformer 23 | sourceAddrTON uint8 24 | sourceAddrNPI uint8 25 | destAddrTON uint8 26 | destAddrNPI uint8 27 | } 28 | 29 | // New instantiates SMTP message notifier. 30 | func New(cfg Config) consumers.Notifier { 31 | t := &smpp.Transmitter{ 32 | Addr: cfg.Address, 33 | User: cfg.Username, 34 | Passwd: cfg.Password, 35 | SystemType: cfg.SystemType, 36 | RespTimeout: 3 * time.Second, 37 | } 38 | t.Bind() 39 | ret := ¬ifier{ 40 | transmitter: t, 41 | transformer: json.New([]json.TimeField{}), 42 | sourceAddrTON: cfg.SourceAddrTON, 43 | destAddrTON: cfg.DestAddrTON, 44 | sourceAddrNPI: cfg.SourceAddrNPI, 45 | destAddrNPI: cfg.DestAddrNPI, 46 | } 47 | return ret 48 | } 49 | 50 | func (n *notifier) Notify(from string, to []string, msg *messaging.Message) error { 51 | send := &smpp.ShortMessage{ 52 | Src: from, 53 | DstList: to, 54 | Validity: 10 * time.Minute, 55 | SourceAddrTON: n.sourceAddrTON, 56 | DestAddrTON: n.destAddrTON, 57 | SourceAddrNPI: n.sourceAddrNPI, 58 | DestAddrNPI: n.destAddrNPI, 59 | Text: pdutext.Raw(msg.GetPayload()), 60 | Register: pdufield.NoDeliveryReceipt, 61 | } 62 | _, err := n.transmitter.Submit(send) 63 | if err != nil { 64 | return err 65 | } 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /consumers/notifiers/smtp/notifier.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package smtp 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/absmach/magistrala/internal/email" 10 | "github.com/absmach/supermq/consumers" 11 | "github.com/absmach/supermq/pkg/messaging" 12 | ) 13 | 14 | const ( 15 | footer = "Sent by SuperMQ SMTP Notification" 16 | contentTemplate = "A publisher with an id %s sent the message over %s with the following values \n %s" 17 | ) 18 | 19 | var _ consumers.Notifier = (*notifier)(nil) 20 | 21 | type notifier struct { 22 | agent *email.Agent 23 | } 24 | 25 | // New instantiates SMTP message notifier. 26 | func New(agent *email.Agent) consumers.Notifier { 27 | return ¬ifier{agent: agent} 28 | } 29 | 30 | func (n *notifier) Notify(from string, to []string, msg *messaging.Message) error { 31 | subject := fmt.Sprintf(`Notification for Channel %s`, msg.GetChannel()) 32 | if msg.GetSubtopic() != "" { 33 | subject = fmt.Sprintf("%s and subtopic %s", subject, msg.GetSubtopic()) 34 | } 35 | 36 | values := string(msg.GetPayload()) 37 | content := fmt.Sprintf(contentTemplate, msg.GetPublisher(), msg.GetProtocol(), values) 38 | 39 | return n.agent.Send(to, from, subject, "", "", content, footer, map[string][]byte{}) 40 | } 41 | -------------------------------------------------------------------------------- /consumers/notifiers/subscriptions.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package notifiers 5 | 6 | import "context" 7 | 8 | // Subscription represents a user Subscription. 9 | type Subscription struct { 10 | ID string 11 | OwnerID string 12 | Contact string 13 | Topic string 14 | } 15 | 16 | // Page represents page metadata with content. 17 | type Page struct { 18 | PageMetadata 19 | Total uint 20 | Subscriptions []Subscription 21 | } 22 | 23 | // PageMetadata contains page metadata that helps navigation. 24 | type PageMetadata struct { 25 | Offset uint 26 | // Limit values less than 0 indicate no limit. 27 | Limit int 28 | Topic string 29 | Contact string 30 | } 31 | 32 | // SubscriptionsRepository specifies a Subscription persistence API. 33 | type SubscriptionsRepository interface { 34 | // Save persists a subscription. Successful operation is indicated by non-nil 35 | // error response. 36 | Save(ctx context.Context, sub Subscription) (string, error) 37 | 38 | // Retrieve retrieves the subscription for the given id. 39 | Retrieve(ctx context.Context, id string) (Subscription, error) 40 | 41 | // RetrieveAll retrieves all the subscriptions for the given page metadata. 42 | RetrieveAll(ctx context.Context, pm PageMetadata) (Page, error) 43 | 44 | // Remove removes the subscription for the given ID. 45 | Remove(ctx context.Context, id string) error 46 | } 47 | -------------------------------------------------------------------------------- /consumers/notifiers/tracing/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package tracing provides tracing instrumentation for SuperMQ WebSocket adapter service. 5 | // 6 | // This package provides tracing middleware for SuperMQ WebSocket adapter service. 7 | // It can be used to trace incoming requests and add tracing capabilities to 8 | // SuperMQ WebSocket adapter service. 9 | // 10 | // For more details about tracing instrumentation for SuperMQ messaging refer 11 | // to the documentation at https://docs.supermq.abstractmachines.fr/tracing/. 12 | package tracing 13 | -------------------------------------------------------------------------------- /consumers/writers/README.md: -------------------------------------------------------------------------------- 1 | # Writers 2 | 3 | Writers provide an implementation of various `message writers`. 4 | Message writers are services that normalize (in `SenML` format) 5 | SuperMQ messages and store them in specific data store. 6 | 7 | Writers are optional services and are treated as plugins. In order to 8 | run writer services, core services must be up and running. For more info 9 | on the platform core services with its dependencies, please check out 10 | the [Docker Compose][compose] file. 11 | 12 | For an in-depth explanation of the usage of `writers`, as well as thorough 13 | understanding of SuperMQ, please check out the [official documentation][doc]. 14 | 15 | [doc]: https://docs.supermq.abstractmachines.fr 16 | [compose]: ../docker/docker-compose.yaml 17 | -------------------------------------------------------------------------------- /consumers/writers/api/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package api contains API-related concerns: endpoint definitions, middlewares 5 | // and all resource representations. 6 | package api 7 | -------------------------------------------------------------------------------- /consumers/writers/api/logging.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build !test 5 | 6 | package api 7 | 8 | import ( 9 | "context" 10 | "log/slog" 11 | "time" 12 | 13 | "github.com/absmach/supermq/consumers" 14 | ) 15 | 16 | var _ consumers.BlockingConsumer = (*loggingMiddleware)(nil) 17 | 18 | type loggingMiddleware struct { 19 | logger *slog.Logger 20 | consumer consumers.BlockingConsumer 21 | } 22 | 23 | // LoggingMiddleware adds logging facilities to the adapter. 24 | func LoggingMiddleware(consumer consumers.BlockingConsumer, logger *slog.Logger) consumers.BlockingConsumer { 25 | return &loggingMiddleware{ 26 | logger: logger, 27 | consumer: consumer, 28 | } 29 | } 30 | 31 | // ConsumeBlocking logs the consume request. It logs the time it took to complete the request. 32 | // If the request fails, it logs the error. 33 | func (lm *loggingMiddleware) ConsumeBlocking(ctx context.Context, msgs interface{}) (err error) { 34 | defer func(begin time.Time) { 35 | args := []any{ 36 | slog.String("duration", time.Since(begin).String()), 37 | } 38 | if err != nil { 39 | args = append(args, slog.Any("error", err)) 40 | lm.logger.Warn("Blocking consumer failed to consume messages successfully", args...) 41 | return 42 | } 43 | lm.logger.Info("Blocking consumer consumed messages successfully", args...) 44 | }(time.Now()) 45 | 46 | return lm.consumer.ConsumeBlocking(ctx, msgs) 47 | } 48 | -------------------------------------------------------------------------------- /consumers/writers/api/metrics.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build !test 5 | 6 | package api 7 | 8 | import ( 9 | "context" 10 | "time" 11 | 12 | "github.com/absmach/supermq/consumers" 13 | "github.com/go-kit/kit/metrics" 14 | ) 15 | 16 | var _ consumers.BlockingConsumer = (*metricsMiddleware)(nil) 17 | 18 | type metricsMiddleware struct { 19 | counter metrics.Counter 20 | latency metrics.Histogram 21 | consumer consumers.BlockingConsumer 22 | } 23 | 24 | // MetricsMiddleware returns new message repository 25 | // with Save method wrapped to expose metrics. 26 | func MetricsMiddleware(consumer consumers.BlockingConsumer, counter metrics.Counter, latency metrics.Histogram) consumers.BlockingConsumer { 27 | return &metricsMiddleware{ 28 | counter: counter, 29 | latency: latency, 30 | consumer: consumer, 31 | } 32 | } 33 | 34 | // ConsumeBlocking instruments ConsumeBlocking method with metrics. 35 | func (mm *metricsMiddleware) ConsumeBlocking(ctx context.Context, msgs interface{}) error { 36 | defer func(begin time.Time) { 37 | mm.counter.With("method", "consume").Add(1) 38 | mm.latency.With("method", "consume").Observe(time.Since(begin).Seconds()) 39 | }(time.Now()) 40 | return mm.consumer.ConsumeBlocking(ctx, msgs) 41 | } 42 | -------------------------------------------------------------------------------- /consumers/writers/api/transport.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package api 5 | 6 | import ( 7 | "net/http" 8 | 9 | "github.com/absmach/supermq" 10 | "github.com/go-chi/chi/v5" 11 | "github.com/prometheus/client_golang/prometheus/promhttp" 12 | ) 13 | 14 | // MakeHandler returns a HTTP API handler with health check and metrics. 15 | func MakeHandler(svcName, instanceID string) http.Handler { 16 | r := chi.NewRouter() 17 | r.Get("/health", supermq.Health(svcName, instanceID)) 18 | r.Handle("/metrics", promhttp.Handler()) 19 | 20 | return r 21 | } 22 | -------------------------------------------------------------------------------- /consumers/writers/brokers/brokers_nats.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build !rabbitmq 5 | // +build !rabbitmq 6 | 7 | package brokers 8 | 9 | import ( 10 | "context" 11 | "log/slog" 12 | "time" 13 | 14 | "github.com/absmach/supermq/pkg/messaging" 15 | broker "github.com/absmach/supermq/pkg/messaging/nats" 16 | "github.com/nats-io/nats.go/jetstream" 17 | ) 18 | 19 | const ( 20 | AllTopic = "writers.>" 21 | 22 | prefix = "writers" 23 | ) 24 | 25 | var cfg = jetstream.StreamConfig{ 26 | Name: "writers", 27 | Description: "SuperMQ Rules Engine stream for handling internal messages", 28 | Subjects: []string{"writers.>"}, 29 | Retention: jetstream.LimitsPolicy, 30 | MaxMsgsPerSubject: 1e6, 31 | MaxAge: time.Hour * 24, 32 | MaxMsgSize: 1024 * 1024, 33 | Discard: jetstream.DiscardOld, 34 | Storage: jetstream.FileStorage, 35 | } 36 | 37 | func NewPubSub(ctx context.Context, url string, logger *slog.Logger) (messaging.PubSub, error) { 38 | pb, err := broker.NewPubSub(ctx, url, logger, broker.Prefix(prefix), broker.JSStreamConfig(cfg)) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | return pb, nil 44 | } 45 | 46 | func NewPublisher(ctx context.Context, url string) (messaging.Publisher, error) { 47 | pb, err := broker.NewPublisher(ctx, url, broker.Prefix(prefix), broker.JSStreamConfig(cfg)) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | return pb, nil 53 | } 54 | -------------------------------------------------------------------------------- /consumers/writers/brokers/brokers_rabbitmq.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build rabbitmq 5 | // +build rabbitmq 6 | 7 | package brokers 8 | 9 | import ( 10 | "context" 11 | "log/slog" 12 | 13 | "github.com/absmach/supermq/pkg/messaging" 14 | broker "github.com/absmach/supermq/pkg/messaging/rabbitmq" 15 | ) 16 | 17 | const ( 18 | AllTopic = "writers.#" 19 | 20 | exchangeName = "writers" 21 | prefix = "writers" 22 | ) 23 | 24 | func NewPubSub(_ context.Context, url string, logger *slog.Logger) (messaging.PubSub, error) { 25 | pb, err := broker.NewPubSub(url, logger, broker.Prefix(prefix), broker.Exchange(exchangeName)) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | return pb, nil 31 | } 32 | 33 | func NewPublisher(_ context.Context, url string) (messaging.Publisher, error) { 34 | pb, err := broker.NewPublisher(url, broker.Prefix(prefix), broker.Exchange(exchangeName)) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | return pb, nil 40 | } 41 | -------------------------------------------------------------------------------- /consumers/writers/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package writers contain the domain concept definitions needed to 5 | // support SuperMQ writer services functionality. 6 | package writers 7 | -------------------------------------------------------------------------------- /consumers/writers/postgres/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package postgres contains repository implementations using Postgres as 5 | // the underlying database. 6 | package postgres 7 | -------------------------------------------------------------------------------- /consumers/writers/postgres/init.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package postgres 5 | 6 | import migrate "github.com/rubenv/sql-migrate" 7 | 8 | // Migration of postgres-writer. 9 | func Migration() *migrate.MemoryMigrationSource { 10 | return &migrate.MemoryMigrationSource{ 11 | Migrations: []*migrate.Migration{ 12 | { 13 | Id: "messages_1", 14 | Up: []string{ 15 | `CREATE TABLE IF NOT EXISTS messages ( 16 | id UUID, 17 | channel UUID, 18 | subtopic VARCHAR(254), 19 | publisher UUID, 20 | protocol TEXT, 21 | name TEXT, 22 | unit TEXT, 23 | value FLOAT, 24 | string_value TEXT, 25 | bool_value BOOL, 26 | data_value BYTEA, 27 | sum FLOAT, 28 | time FLOAT, 29 | update_time FLOAT, 30 | PRIMARY KEY (id) 31 | )`, 32 | }, 33 | Down: []string{ 34 | "DROP TABLE messages", 35 | }, 36 | }, 37 | { 38 | Id: "messages_2", 39 | Up: []string{ 40 | `ALTER TABLE messages DROP CONSTRAINT messages_pkey`, 41 | `ALTER TABLE messages ADD PRIMARY KEY (time, publisher, subtopic, name)`, 42 | }, 43 | }, 44 | }, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /consumers/writers/postgres/setup_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package postgres_test contains tests for PostgreSQL repository 5 | // implementations. 6 | package postgres_test 7 | 8 | import ( 9 | "fmt" 10 | "log" 11 | "os" 12 | "testing" 13 | 14 | "github.com/absmach/magistrala/consumers/writers/postgres" 15 | pgclient "github.com/absmach/supermq/pkg/postgres" 16 | "github.com/jmoiron/sqlx" 17 | "github.com/ory/dockertest/v3" 18 | "github.com/ory/dockertest/v3/docker" 19 | ) 20 | 21 | var db *sqlx.DB 22 | 23 | func TestMain(m *testing.M) { 24 | pool, err := dockertest.NewPool("") 25 | if err != nil { 26 | log.Fatalf("Could not connect to docker: %s", err) 27 | } 28 | 29 | container, err := pool.RunWithOptions(&dockertest.RunOptions{ 30 | Repository: "postgres", 31 | Tag: "16.2-alpine", 32 | Env: []string{ 33 | "POSTGRES_USER=test", 34 | "POSTGRES_PASSWORD=test", 35 | "POSTGRES_DB=test", 36 | "listen_addresses = '*'", 37 | }, 38 | }, func(config *docker.HostConfig) { 39 | config.AutoRemove = true 40 | config.RestartPolicy = docker.RestartPolicy{Name: "no"} 41 | }) 42 | if err != nil { 43 | log.Fatalf("Could not start container: %s", err) 44 | } 45 | 46 | port := container.GetPort("5432/tcp") 47 | 48 | if err := pool.Retry(func() error { 49 | url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port) 50 | db, err = sqlx.Open("pgx", url) 51 | if err != nil { 52 | return err 53 | } 54 | return db.Ping() 55 | }); err != nil { 56 | log.Fatalf("Could not connect to docker: %s", err) 57 | } 58 | 59 | dbConfig := pgclient.Config{ 60 | Host: "localhost", 61 | Port: port, 62 | User: "test", 63 | Pass: "test", 64 | Name: "test", 65 | SSLMode: "disable", 66 | SSLCert: "", 67 | SSLKey: "", 68 | SSLRootCert: "", 69 | } 70 | 71 | db, err = pgclient.Setup(dbConfig, *postgres.Migration()) 72 | if err != nil { 73 | log.Fatalf("Could not setup test DB connection: %s", err) 74 | } 75 | 76 | code := m.Run() 77 | 78 | // Defers will not be run when using os.Exit 79 | db.Close() 80 | if err := pool.Purge(container); err != nil { 81 | log.Fatalf("Could not purge container: %s", err) 82 | } 83 | 84 | os.Exit(code) 85 | } 86 | -------------------------------------------------------------------------------- /consumers/writers/timescale/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package timescale contains repository implementations using Timescale as 5 | // the underlying database. 6 | package timescale 7 | -------------------------------------------------------------------------------- /consumers/writers/timescale/setup_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package timescale_test contains tests for TimescaleSQL repository 5 | // implementations. 6 | package timescale_test 7 | 8 | import ( 9 | "fmt" 10 | "log" 11 | "os" 12 | "testing" 13 | 14 | "github.com/absmach/magistrala/consumers/writers/timescale" 15 | pgclient "github.com/absmach/supermq/pkg/postgres" 16 | "github.com/jmoiron/sqlx" 17 | "github.com/ory/dockertest/v3" 18 | "github.com/ory/dockertest/v3/docker" 19 | ) 20 | 21 | var db *sqlx.DB 22 | 23 | func TestMain(m *testing.M) { 24 | pool, err := dockertest.NewPool("") 25 | if err != nil { 26 | log.Fatalf("Could not connect to docker: %s", err) 27 | } 28 | 29 | container, err := pool.RunWithOptions(&dockertest.RunOptions{ 30 | Repository: "timescale/timescaledb", 31 | Tag: "2.13.1-pg16", 32 | Env: []string{ 33 | "POSTGRES_USER=test", 34 | "POSTGRES_PASSWORD=test", 35 | "POSTGRES_DB=test", 36 | "listen_addresses = '*'", 37 | }, 38 | }, func(config *docker.HostConfig) { 39 | config.AutoRemove = true 40 | config.RestartPolicy = docker.RestartPolicy{Name: "no"} 41 | }) 42 | if err != nil { 43 | log.Fatalf("Could not start container: %s", err) 44 | } 45 | 46 | port := container.GetPort("5432/tcp") 47 | 48 | if err := pool.Retry(func() error { 49 | url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port) 50 | db, err = sqlx.Open("pgx", url) 51 | if err != nil { 52 | return err 53 | } 54 | return db.Ping() 55 | }); err != nil { 56 | log.Fatalf("Could not connect to docker: %s", err) 57 | } 58 | 59 | dbConfig := pgclient.Config{ 60 | Host: "localhost", 61 | Port: port, 62 | User: "test", 63 | Pass: "test", 64 | Name: "test", 65 | SSLMode: "disable", 66 | SSLCert: "", 67 | SSLKey: "", 68 | SSLRootCert: "", 69 | } 70 | 71 | db, err = pgclient.Setup(dbConfig, *timescale.Migration()) 72 | if err != nil { 73 | log.Fatalf("Could not setup test DB connection: %s", err) 74 | } 75 | 76 | code := m.Run() 77 | 78 | // Defers will not be run when using os.Exit 79 | db.Close() 80 | if err := pool.Purge(container); err != nil { 81 | log.Fatalf("Could not purge container: %s", err) 82 | } 83 | 84 | os.Exit(code) 85 | } 86 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // package magistrala acts as an umbrella package containing multiple different 5 | // microservices and defines all shared domain concepts. 6 | package magistrala 7 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | FROM golang:1.24.3-alpine AS builder 5 | ARG SVC 6 | ARG GOARCH 7 | ARG GOARM 8 | ARG VERSION 9 | ARG COMMIT 10 | ARG TIME 11 | 12 | WORKDIR /go/src/github.com/absmach/magistrala 13 | COPY . . 14 | RUN apk update \ 15 | && apk add make upx\ 16 | && make $SVC \ 17 | && upx build/$SVC \ 18 | && mv build/$SVC /exe 19 | 20 | FROM scratch 21 | # Certificates are needed so that mailing util can work. 22 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 23 | COPY --from=builder /exe / 24 | ENTRYPOINT ["/exe"] 25 | -------------------------------------------------------------------------------- /docker/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | FROM scratch 5 | ARG SVC 6 | COPY $SVC /exe 7 | COPY --from=alpine:latest /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 8 | ENTRYPOINT ["/exe"] 9 | -------------------------------------------------------------------------------- /docker/addons/postgres-writer/config.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # To listen all messsage broker subjects use default value "writers.>". 5 | # To subscribe to specific subjects use values starting by "writers." and 6 | # followed by a subtopic (e.g ["writers.<channel_id>.sub.topic.x", ...]). 7 | ["subscriber"] 8 | subjects = ["writers.>"] 9 | 10 | [transformer] 11 | # SenML or JSON 12 | format = "senml" 13 | # Used if format is SenML 14 | content_type = "application/senml+json" 15 | # Used as timestamp fields if format is JSON 16 | time_fields = [{ field_name = "seconds_key", field_format = "unix", location = "UTC"}, 17 | { field_name = "millis_key", field_format = "unix_ms", location = "UTC"}, 18 | { field_name = "micros_key", field_format = "unix_us", location = "UTC"}, 19 | { field_name = "nanos_key", field_format = "unix_ns", location = "UTC"}] 20 | -------------------------------------------------------------------------------- /docker/addons/prometheus/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # This docker-compose file contains optional Prometheus and Grafana service for Magistrala platform. 5 | # Since this service is optional, this file is dependent of docker-compose.yaml file 6 | # from <project_root>/docker. In order to run this service, execute command: 7 | # docker compose -f docker/addons/prometheus/docker-compose.yaml up 8 | # from project root. 9 | 10 | networks: 11 | magistrala-base-net: 12 | 13 | volumes: 14 | magistrala-prometheus-volume: 15 | 16 | services: 17 | promethues: 18 | image: prom/prometheus:v2.49.1 19 | container_name: magistrala-prometheus 20 | restart: on-failure 21 | ports: 22 | - ${MG_PROMETHEUS_PORT}:${MG_PROMETHEUS_PORT} 23 | networks: 24 | - magistrala-base-net 25 | volumes: 26 | - type: bind 27 | source: ./metrics/prometheus.yaml 28 | target: /etc/prometheus/prometheus.yaml 29 | - magistrala-prometheus-volume:/prometheus 30 | 31 | grafana: 32 | image: grafana/grafana:10.2.3 33 | container_name: magistrala-grafana 34 | depends_on: 35 | - promethues 36 | restart: on-failure 37 | ports: 38 | - ${MG_GRAFANA_PORT}:${MG_GRAFANA_PORT} 39 | environment: 40 | - GF_SECURITY_ADMIN_USER=${MG_GRAFANA_ADMIN_USER} 41 | - GF_SECURITY_ADMIN_PASSWORD=${MG_GRAFANA_ADMIN_PASSWORD} 42 | networks: 43 | - magistrala-base-net 44 | volumes: 45 | - type: bind 46 | source: ./grafana/datasource.yaml 47 | target: /etc/grafana/provisioning/datasources/datasource.yaml 48 | - type: bind 49 | source: ./grafana/dashboard.yaml 50 | target: /etc/grafana/provisioning/dashboards/main.yaml 51 | - type: bind 52 | source: ./grafana/example-dashboard.json 53 | target: /var/lib/grafana/dashboards/example-dashboard.json 54 | -------------------------------------------------------------------------------- /docker/addons/prometheus/grafana/dashboard.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | apiVersion: 1 5 | 6 | providers: 7 | - name: "Dashboard provider" 8 | orgId: 1 9 | type: file 10 | disableDeletion: false 11 | updateIntervalSeconds: 10 12 | allowUiUpdates: false 13 | options: 14 | path: /var/lib/grafana/dashboards 15 | foldersFromFilesStructure: true 16 | -------------------------------------------------------------------------------- /docker/addons/prometheus/grafana/datasource.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | apiVersion: 1 5 | 6 | datasources: 7 | - name: Prometheus 8 | type: prometheus 9 | url: http://magistrala-prometheus:9090 10 | isDefault: true 11 | access: proxy 12 | editable: true 13 | -------------------------------------------------------------------------------- /docker/addons/prometheus/metrics/prometheus.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | global: 5 | scrape_interval: 15s 6 | evaluation_interval: 15s 7 | 8 | scrape_configs: 9 | - job_name: 'magistrala' 10 | honor_timestamps: true 11 | scrape_interval: 15s 12 | scrape_timeout: 10s 13 | metrics_path: /metrics 14 | follow_redirects: true 15 | enable_http2: true 16 | static_configs: 17 | - targets: 18 | - magistrala-things:9000 19 | - magistrala-users:9002 20 | - magistrala-http:8008 21 | - magistrala-ws:8186 22 | - magistrala-coap:5683 23 | -------------------------------------------------------------------------------- /docker/addons/provision/configs/config.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | [bootstrap] 5 | [bootstrap.content] 6 | [bootstrap.content.agent.edgex] 7 | url = "http://localhost:48090/api/v1/" 8 | 9 | [bootstrap.content.agent.log] 10 | level = "info" 11 | 12 | [bootstrap.content.agent.mqtt] 13 | mtls = false 14 | qos = 0 15 | retain = false 16 | skip_tls_ver = true 17 | url = "localhost:1883" 18 | 19 | [bootstrap.content.agent.server] 20 | nats_url = "localhost:4222" 21 | port = "9000" 22 | 23 | [bootstrap.content.agent.heartbeat] 24 | interval = "30s" 25 | 26 | [bootstrap.content.agent.terminal] 27 | session_timeout = "30s" 28 | 29 | 30 | [bootstrap.content.export.exp] 31 | log_level = "debug" 32 | nats = "nats://localhost:4222" 33 | port = "8172" 34 | cache_url = "localhost:6379" 35 | cache_pass = "" 36 | cache_db = "0" 37 | 38 | [bootstrap.content.export.mqtt] 39 | ca_path = "ca.crt" 40 | cert_path = "thing.crt" 41 | channel = "" 42 | host = "tcp://localhost:1883" 43 | mtls = false 44 | password = "" 45 | priv_key_path = "thing.key" 46 | qos = 0 47 | retain = false 48 | skip_tls_ver = false 49 | username = "" 50 | 51 | [[bootstrap.content.export.routes]] 52 | mqtt_topic = "" 53 | nats_topic = ">" 54 | subtopic = "" 55 | type = "plain" 56 | workers = 10 57 | 58 | [[things]] 59 | name = "thing" 60 | 61 | [things.metadata] 62 | external_id = "xxxxxx" 63 | 64 | [[channels]] 65 | name = "control-channel" 66 | 67 | [channels.metadata] 68 | type = "control" 69 | 70 | [[channels]] 71 | name = "data-channel" 72 | 73 | [channels.metadata] 74 | type = "data" 75 | -------------------------------------------------------------------------------- /docker/addons/provision/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # This docker-compose file contains optional provision services. Since it's optional, this file is 5 | # dependent of docker-compose file from <project_root>/docker. In order to run this services, execute command: 6 | # docker compose -f docker/docker-compose.yaml -f docker/addons/provision/docker-compose.yaml up 7 | # from project root. 8 | 9 | networks: 10 | magistrala-base-net: 11 | 12 | services: 13 | provision: 14 | image: ghcr.io/absmach/magistrala/provision:${MG_RELEASE_TAG} 15 | container_name: magistrala-provision 16 | restart: on-failure 17 | networks: 18 | - magistrala-base-net 19 | ports: 20 | - ${MG_PROVISION_HTTP_PORT}:${MG_PROVISION_HTTP_PORT} 21 | environment: 22 | MG_PROVISION_LOG_LEVEL: ${MG_PROVISION_LOG_LEVEL} 23 | MG_PROVISION_HTTP_PORT: ${MG_PROVISION_HTTP_PORT} 24 | MG_PROVISION_CONFIG_FILE: ${MG_PROVISION_CONFIG_FILE} 25 | MG_PROVISION_ENV_CLIENTS_TLS: ${MG_PROVISION_ENV_CLIENTS_TLS} 26 | MG_PROVISION_SERVER_CERT: ${MG_PROVISION_SERVER_CERT} 27 | MG_PROVISION_SERVER_KEY: ${MG_PROVISION_SERVER_KEY} 28 | MG_PROVISION_USERS_LOCATION: ${MG_PROVISION_USERS_LOCATION} 29 | MG_PROVISION_THINGS_LOCATION: ${MG_PROVISION_THINGS_LOCATION} 30 | MG_PROVISION_USER: ${MG_PROVISION_USER} 31 | MG_PROVISION_USERNAME: ${MG_PROVISION_USERNAME} 32 | MG_PROVISION_PASS: ${MG_PROVISION_PASS} 33 | MG_PROVISION_API_KEY: ${MG_PROVISION_API_KEY} 34 | MG_PROVISION_CERTS_SVC_URL: ${MG_PROVISION_CERTS_SVC_URL} 35 | MG_PROVISION_X509_PROVISIONING: ${MG_PROVISION_X509_PROVISIONING} 36 | MG_PROVISION_BS_SVC_URL: ${MG_PROVISION_BS_SVC_URL} 37 | MG_PROVISION_BS_CONFIG_PROVISIONING: ${MG_PROVISION_BS_CONFIG_PROVISIONING} 38 | MG_PROVISION_BS_AUTO_WHITELIST: ${MG_PROVISION_BS_AUTO_WHITELIST} 39 | MG_PROVISION_BS_CONTENT: ${MG_PROVISION_BS_CONTENT} 40 | MG_PROVISION_CERTS_HOURS_VALID: ${MG_PROVISION_CERTS_HOURS_VALID} 41 | SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY} 42 | MG_PROVISION_INSTANCE_ID: ${MG_PROVISION_INSTANCE_ID} 43 | volumes: 44 | - ./configs:/configs 45 | - ../../ssl/certs/ca.key:/etc/ssl/certs/ca.key 46 | - ../../ssl/certs/ca.crt:/etc/ssl/certs/ca.crt 47 | -------------------------------------------------------------------------------- /docker/addons/timescale-writer/config.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # To listen all messsage broker subjects use default value "writers.>". 5 | # To subscribe to specific subjects use values starting by "writers." and 6 | # followed by a subtopic (e.g ["writers.<channel_id>.sub.topic.x", ...]). 7 | ["subscriber"] 8 | subjects = ["writers.>"] 9 | -------------------------------------------------------------------------------- /docker/addons/vault/config.hcl: -------------------------------------------------------------------------------- 1 | storage "file" { 2 | path = "/vault/file" 3 | } 4 | 5 | listener "tcp" { 6 | address = "0.0.0.0:8200" 7 | tls_disable = 1 8 | } 9 | 10 | ui = true 11 | -------------------------------------------------------------------------------- /docker/addons/vault/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # This docker-compose file contains optional Vault service for Magistrala platform. 5 | # Since this is optional, this file is dependent of docker-compose file 6 | # from <project_root>/docker. In order to run these services, execute command: 7 | # docker compose -f docker/docker-compose.yaml -f docker/addons/vault/docker-compose.yaml up 8 | # from project root. Vault default port (8200) is exposed, so you can use Vault CLI tool for 9 | # vault inspection and administration, as well as access the UI. 10 | 11 | networks: 12 | magistrala-base-net: 13 | 14 | volumes: 15 | magistrala-vault-volume: 16 | 17 | services: 18 | vault: 19 | image: hashicorp/vault:1.15.4 20 | container_name: magistrala-vault 21 | ports: 22 | - ${MG_VAULT_PORT}:8200 23 | networks: 24 | - magistrala-base-net 25 | volumes: 26 | - magistrala-vault-volume:/vault/file 27 | - magistrala-vault-volume:/vault/logs 28 | - ./config.hcl:/vault/config/config.hcl 29 | - ./entrypoint.sh:/entrypoint.sh 30 | environment: 31 | VAULT_ADDR: http://127.0.0.1:${MG_VAULT_PORT} 32 | MG_VAULT_PORT: ${MG_VAULT_PORT} 33 | MG_VAULT_UNSEAL_KEY_1: ${MG_VAULT_UNSEAL_KEY_1} 34 | MG_VAULT_UNSEAL_KEY_2: ${MG_VAULT_UNSEAL_KEY_2} 35 | MG_VAULT_UNSEAL_KEY_3: ${MG_VAULT_UNSEAL_KEY_3} 36 | entrypoint: /bin/sh 37 | command: /entrypoint.sh 38 | cap_add: 39 | - IPC_LOCK 40 | -------------------------------------------------------------------------------- /docker/addons/vault/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/dumb-init /bin/sh 2 | # Copyright (c) Abstract Machines 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | VAULT_CONFIG_DIR=/vault/config 6 | 7 | docker-entrypoint.sh server & 8 | VAULT_PID=$! 9 | 10 | sleep 2 11 | 12 | echo $MG_VAULT_UNSEAL_KEY_1 13 | echo $MG_VAULT_UNSEAL_KEY_2 14 | echo $MG_VAULT_UNSEAL_KEY_3 15 | 16 | if [[ ! -z "${MG_VAULT_UNSEAL_KEY_1}" ]] && 17 | [[ ! -z "${MG_VAULT_UNSEAL_KEY_2}" ]] && 18 | [[ ! -z "${MG_VAULT_UNSEAL_KEY_3}" ]]; then 19 | echo "Unsealing Vault" 20 | vault operator unseal ${MG_VAULT_UNSEAL_KEY_1} 21 | vault operator unseal ${MG_VAULT_UNSEAL_KEY_2} 22 | vault operator unseal ${MG_VAULT_UNSEAL_KEY_3} 23 | fi 24 | 25 | wait $VAULT_PID -------------------------------------------------------------------------------- /docker/addons/vault/scripts/.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | data 5 | magistrala_things_certs_issue.hcl 6 | -------------------------------------------------------------------------------- /docker/addons/vault/scripts/magistrala_things_certs_issue.template.hcl: -------------------------------------------------------------------------------- 1 | 2 | # Allow issue certificate with role with default issuer from Intermediate PKI 3 | path "${MG_VAULT_PKI_INT_PATH}/issue/${MG_VAULT_PKI_INT_THINGS_CERTS_ROLE_NAME}" { 4 | capabilities = ["create", "update"] 5 | } 6 | 7 | ## Revole certificate from Intermediate PKI 8 | path "${MG_VAULT_PKI_INT_PATH}/revoke" { 9 | capabilities = ["create", "update"] 10 | } 11 | 12 | ## List Revoked Certificates from Intermediate PKI 13 | path "${MG_VAULT_PKI_INT_PATH}/certs/revoked" { 14 | capabilities = ["list"] 15 | } 16 | 17 | 18 | ## List Certificates from Intermediate PKI 19 | path "${MG_VAULT_PKI_INT_PATH}/certs" { 20 | capabilities = ["list"] 21 | } 22 | 23 | ## Read Certificate from Intermediate PKI 24 | path "${MG_VAULT_PKI_INT_PATH}/cert/+" { 25 | capabilities = ["read"] 26 | } 27 | path "${MG_VAULT_PKI_INT_PATH}/cert/+/raw" { 28 | capabilities = ["read"] 29 | } 30 | path "${MG_VAULT_PKI_INT_PATH}/cert/+/raw/pem" { 31 | capabilities = ["read"] 32 | } 33 | -------------------------------------------------------------------------------- /docker/addons/vault/scripts/vault_cmd.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | # Copyright (c) Abstract Machines 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | vault() { 6 | if is_container_running "magistrala-vault"; then 7 | docker exec -it magistrala-vault vault "$@" 8 | else 9 | if which vault &> /dev/null; then 10 | $(which vault) "$@" 11 | else 12 | echo "magistrala-vault container or vault command not found. Please refer to the documentation: https://github.com/absmach/magistrala/blob/main/docker/addons/vault/README.md" 13 | fi 14 | fi 15 | } 16 | 17 | is_container_running() { 18 | local container_name="$1" 19 | if [ "$(docker inspect --format '{{.State.Running}}' "$container_name" 2>/dev/null)" = "true" ]; then 20 | return 0 21 | else 22 | return 1 23 | fi 24 | } 25 | -------------------------------------------------------------------------------- /docker/addons/vault/scripts/vault_copy_env.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | # Copyright (c) Abstract Machines 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | set -euo pipefail 6 | 7 | scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" 8 | 9 | # default env file path 10 | env_file="docker/.env" 11 | 12 | while [[ "$#" -gt 0 ]]; do 13 | case $1 in 14 | --env-file) 15 | if [[ -z "${2:-}" ]]; then 16 | echo "Error: --env-file requires a non-empty option argument." 17 | exit 1 18 | fi 19 | env_file="$2" 20 | if [[ ! -f "$env_file" ]]; then 21 | echo "Error: .env file not found at $env_file" 22 | exit 1 23 | fi 24 | shift 25 | ;; 26 | *) 27 | echo "Unknown parameter passed: $1" 28 | exit 1 29 | ;; 30 | esac 31 | shift 32 | done 33 | 34 | write_env() { 35 | if [ -e "$scriptdir/data/secrets" ]; then 36 | sed -i "s,MG_VAULT_UNSEAL_KEY_1=.*,MG_VAULT_UNSEAL_KEY_1=$(awk -F ": " '$1 == "Unseal Key 1" {print $2}' $scriptdir/data/secrets)," "$env_file" 37 | sed -i "s,MG_VAULT_UNSEAL_KEY_2=.*,MG_VAULT_UNSEAL_KEY_2=$(awk -F ": " '$1 == "Unseal Key 2" {print $2}' $scriptdir/data/secrets)," "$env_file" 38 | sed -i "s,MG_VAULT_UNSEAL_KEY_3=.*,MG_VAULT_UNSEAL_KEY_3=$(awk -F ": " '$1 == "Unseal Key 3" {print $2}' $scriptdir/data/secrets)," "$env_file" 39 | sed -i "s,MG_VAULT_TOKEN=.*,MG_VAULT_TOKEN=$(awk -F ": " '$1 == "Initial Root Token" {print $2}' $scriptdir/data/secrets)," "$env_file" 40 | echo "Vault environment variables are set successfully in $env_file" 41 | else 42 | echo "Error: Source file '$scriptdir/data/secrets' not found." 43 | fi 44 | } 45 | 46 | write_env 47 | -------------------------------------------------------------------------------- /docker/addons/vault/scripts/vault_init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | # Copyright (c) Abstract Machines 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | set -euo pipefail 6 | 7 | scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" 8 | 9 | # default env file path 10 | env_file="docker/.env" 11 | 12 | while [[ "$#" -gt 0 ]]; do 13 | case $1 in 14 | --env-file) 15 | if [[ -z "${2:-}" ]]; then 16 | echo "Error: --env-file requires a non-empty option argument." 17 | exit 1 18 | fi 19 | env_file="$2" 20 | if [[ ! -f "$env_file" ]]; then 21 | echo "Error: .env file not found at $env_file" 22 | exit 1 23 | fi 24 | shift 25 | ;; 26 | *) 27 | echo "Unknown parameter passed: $1" 28 | exit 1 29 | ;; 30 | esac 31 | shift 32 | done 33 | 34 | readDotEnv() { 35 | set -o allexport 36 | source "$env_file" 37 | set +o allexport 38 | } 39 | 40 | source "$scriptdir/vault_cmd.sh" 41 | 42 | readDotEnv 43 | 44 | mkdir -p "$scriptdir/data" 45 | 46 | vault operator init -address="$MG_VAULT_ADDR" 2>&1 | tee >(sed -r 's/\x1b\[[0-9;]*m//g' > "$scriptdir/data/secrets") 47 | -------------------------------------------------------------------------------- /docker/addons/vault/scripts/vault_unseal.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | # Copyright (c) Abstract Machines 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | set -euo pipefail 6 | 7 | scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" 8 | 9 | # default env file path 10 | env_file="docker/.env" 11 | 12 | while [[ "$#" -gt 0 ]]; do 13 | case $1 in 14 | --env-file) 15 | if [[ -z "${2:-}" ]]; then 16 | echo "Error: --env-file requires a non-empty option argument." 17 | exit 1 18 | fi 19 | env_file="$2" 20 | if [[ ! -f "$env_file" ]]; then 21 | echo "Error: .env file not found at $env_file" 22 | exit 1 23 | fi 24 | shift 25 | ;; 26 | *) 27 | echo "Unknown parameter passed: $1" 28 | exit 1 29 | ;; 30 | esac 31 | shift 32 | done 33 | 34 | readDotEnv() { 35 | set -o allexport 36 | source "$env_file" 37 | set +o allexport 38 | } 39 | 40 | source "$scriptdir/vault_cmd.sh" 41 | 42 | readDotEnv 43 | 44 | vault operator unseal -address=${MG_VAULT_ADDR} ${MG_VAULT_UNSEAL_KEY_1} 45 | vault operator unseal -address=${MG_VAULT_ADDR} ${MG_VAULT_UNSEAL_KEY_2} 46 | vault operator unseal -address=${MG_VAULT_ADDR} ${MG_VAULT_UNSEAL_KEY_3} 47 | -------------------------------------------------------------------------------- /docker/config.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # To listen all messsage broker subjects use default value "channels.>". 5 | # To subscribe to specific subjects use values starting by "channels." and 6 | # followed by a subtopic (e.g ["channels.<channel_id>.sub.topic.x", ...]). 7 | [subscriber] 8 | subjects = ["channels.>"] 9 | -------------------------------------------------------------------------------- /docker/nginx/.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | snippets/mqtt-upstream.conf 5 | snippets/mqtt-ws-upstream.conf 6 | -------------------------------------------------------------------------------- /docker/nginx/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/ash 2 | # Copyright (c) Abstract Machines 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | if [ -z "$SMQ_MQTT_CLUSTER" ]; then 6 | envsubst '${SMQ_MQTT_ADAPTER_MQTT_PORT}' </etc/nginx/snippets/mqtt-upstream-single.conf >/etc/nginx/snippets/mqtt-upstream.conf 7 | envsubst '${SMQ_MQTT_ADAPTER_WS_PORT}' </etc/nginx/snippets/mqtt-ws-upstream-single.conf >/etc/nginx/snippets/mqtt-ws-upstream.conf 8 | else 9 | envsubst '${SMQ_MQTT_ADAPTER_MQTT_PORT}' </etc/nginx/snippets/mqtt-upstream-cluster.conf >/etc/nginx/snippets/mqtt-upstream.conf 10 | envsubst '${SMQ_MQTT_ADAPTER_WS_PORT}' </etc/nginx/snippets/mqtt-ws-upstream-cluster.conf >/etc/nginx/snippets/mqtt-ws-upstream.conf 11 | fi 12 | 13 | envsubst ' 14 | ${SMQ_NGINX_SERVER_NAME} 15 | ${SMQ_DOMAINS_HTTP_PORT} 16 | ${SMQ_GROUPS_HTTP_PORT} 17 | ${SMQ_USERS_HTTP_PORT} 18 | ${SMQ_CLIENTS_HTTP_PORT} 19 | ${SMQ_CLIENTS_HTTP_PORT} 20 | ${SMQ_CHANNELS_HTTP_PORT} 21 | ${SMQ_HTTP_ADAPTER_PORT} 22 | ${SMQ_NGINX_MQTT_PORT} 23 | ${SMQ_NGINX_MQTTS_PORT} 24 | ${MG_RE_HTTP_PORT} 25 | ${MG_ALARMS_HTTP_PORT} 26 | ${SMQ_WS_ADAPTER_HTTP_PORT}' </etc/nginx/nginx.conf.template >/etc/nginx/nginx.conf 27 | 28 | exec nginx -g "daemon off;" 29 | -------------------------------------------------------------------------------- /docker/ssl/.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | *grpc-server* 5 | *grpc-client* 6 | *srl 7 | *conf 8 | -------------------------------------------------------------------------------- /docker/ssl/certs/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDyzCCArOgAwIBAgIUDIJg63dQVzoD9nmWi9YPscQwTgIwDQYJKoZIhvcNAQEN 3 | BQAwdTEiMCAGA1UEAwwZTWFnaXN0cmFsYV9TZWxmX1NpZ25lZF9DQTETMBEGA1UE 4 | CgwKTWFnaXN0cmFsYTEWMBQGA1UECwwNbWFnaXN0cmFsYV9jYTEiMCAGCSqGSIb3 5 | DQEJARYTaW5mb0BtYWdpc3RyYWxhLmNvbTAeFw0yMzEwMzAwODE5MDFaFw0yNjEw 6 | MjkwODE5MDFaMHUxIjAgBgNVBAMMGU1hZ2lzdHJhbGFfU2VsZl9TaWduZWRfQ0Ex 7 | EzARBgNVBAoMCk1hZ2lzdHJhbGExFjAUBgNVBAsMDW1hZ2lzdHJhbGFfY2ExIjAg 8 | BgkqhkiG9w0BCQEWE2luZm9AbWFnaXN0cmFsYS5jb20wggEiMA0GCSqGSIb3DQEB 9 | AQUAA4IBDwAwggEKAoIBAQCWNIeGfo/SePOvviJE6UHJhBzWcPfNVbzSF6A42WgB 10 | DEgI3KFr+/rgWMEaCOD4QzCl3Lqa89EgCA7xCgxcqFwEo33SyhAivwoHL2pRVHXn 11 | oee3z9U757T63YLE0qrXQY2cbyChX/OU99rZxyd5l5jUGN7MCu+RYurfTIiYN+Uv 12 | NZdl8a3X84g7fa70EOYas7cTunWUt9x64/jYDoYmn+XPXET1yEU1dQTnKY4cRjhv 13 | HS1u2QsadHKi1hgeILyLbB4u1T5N+WfxFknhFHTu8PVPxfowrVv/xzmxOe0zSZFd 14 | SbhtrmwT4S1wJ4PfUa3+tYZVtjEKKbyObsAW91WzOLS9AgMBAAGjUzBRMB0GA1Ud 15 | DgQWBBQkE4koZctEZpTz9pq6a6s6xg+myTAfBgNVHSMEGDAWgBQkE4koZctEZpTz 16 | 9pq6a6s6xg+myTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IBAQA7 17 | w/oh5U9loJsigf3X3T3jQM8PVmhsUfNMJ3kc1Yumr72S4sGKjdWwuU0vk+B3eQzh 18 | zXAj65BHhs1pXcukeoLR7YcHABEsEMg6lar/E4A+MgAZfZFVSvPpsByIK8I5ARk+ 19 | K1V/lWso+GJJM/lImPPnpvUWBdbntqC5WtjoMMGL9uyV3kVS6yT/kJ2ercnPzhPh 20 | uBkL1ZH3ivDn/0JDY+T8Sfeq08vNWaTcoC7qpPwqXhuT0ytY7oaBS5wmPcvvzpZg 21 | 6zZYPZfhjhdEFYY1hDrrPYNYO72jncUnwQVp3X0DQpSvbxp681hVkcEtwHB2B8l0 22 | tBGhgoH+TqZs0AUjoXM0 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /docker/ssl/certs/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCWNIeGfo/SePOv 3 | viJE6UHJhBzWcPfNVbzSF6A42WgBDEgI3KFr+/rgWMEaCOD4QzCl3Lqa89EgCA7x 4 | CgxcqFwEo33SyhAivwoHL2pRVHXnoee3z9U757T63YLE0qrXQY2cbyChX/OU99rZ 5 | xyd5l5jUGN7MCu+RYurfTIiYN+UvNZdl8a3X84g7fa70EOYas7cTunWUt9x64/jY 6 | DoYmn+XPXET1yEU1dQTnKY4cRjhvHS1u2QsadHKi1hgeILyLbB4u1T5N+WfxFknh 7 | FHTu8PVPxfowrVv/xzmxOe0zSZFdSbhtrmwT4S1wJ4PfUa3+tYZVtjEKKbyObsAW 8 | 91WzOLS9AgMBAAECggEAEOxEq6jFO/WgIPgHROPR42ok1J1AMgx7nGEIjnciImIX 9 | mJYBAtlOM+oUAYKoFBh/2eQTSyN2t4jo5AvZhjP6wBQKeE4HQN7supADRrwBF7KU 10 | WI+MKvZpW81KrzG8CUoLsikMEFpu52UAbYJkZmznzVeq/GqsAKGYLEXjauD7S5Tu 11 | GeGVKO4novus6t3AHnBvfalIQ1JUuJFvcd5ZDhPljlzPbbWdM4WpRPaFZIKmfXft 12 | G7Izt58yPCYwhxohjrunRudyX3oKvmCBUOBXC8HdHzND/dLxwlrVu7OjmXprmC6P 13 | 8ggNpjAPeO8Y6+EKGne1fETNsKgODY/lXGOwECY4eQKBgQDSGi3WuoT/+DecVeSF 14 | GfmavdGCQKOD0kdl7qCeQYAL+SPVz4157AtxZs3idapvlbrc7wvw4Ev1XT7ZmWUj 15 | Lc4/UAITR8EkkFRVbxt2PvV86AiQtmXFguTNEX5vTszRwZ2+eqijZga5niBkqyAi 16 | SRuTwR8WrDZau4mRNnF8bUl8dQKBgQC3BKYifRp4hHqBycHe9rSMZ8Xz+ZOy+IFA 17 | vYap1Az+e8KuqlmD9Kfpp2Mjba1+HL5WKeTJGpFE7bhvb/xMPJgbMgtQ/cw4uDJ/ 18 | fwv4m6arf76ebOhaZtkT1vD4NyiyB+z6xP0TRgQRr2Or98XBSvGAYDXIn5vL7fUg 19 | KrDF0ePuKQKBgDfaOcFRiDW7uJzYwI0ZoJ8gQufLYyyR4+UXEJ/BbdbA/mPCbyuw 20 | MkKNP8Ip4YsUVL6S1avNFKQ/i4uxGY/Gh4ORM1wIwTGFJMYpaTV/+yafUFeYBWoC 21 | J+zT77aLTiucuuB+HwKBBtylSps4WqyCntAikK8oTLLGFAYEYRrgup5ZAoGAbQ8j 22 | JNghxwFCs0aT9ZZTfnt0NW9auUJmWzrVHSxUVe1P1J+EWiKXUJ/DbuAzizv7nAK4 23 | 57GiMU3rItS7pn5RMZt/rNKgOIhi5yDA9HNkPTwRTfyd9QjmgHEMBQ1xfa1FZSWv 24 | nSWS1SsLnPU37XgIMzShuByMTVhOQs3NqwPo7AkCgYAf8AzQNjFCoTwU3SJezJ4H 25 | 9j1jvMO232hAl8UDNtqvJ1APn87tOtnfX48OMoRrP9kKI0oygE3pq7rFxu1qmTns 26 | Zir0+KLeWGg58fSZkUEAp6kbO5CKwoeVAY9EMgd7BYBqlXLqUNfdH0L+KUOFKHha 27 | 7e82VxpgBeskzAqN1e7YRA== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /docker/ssl/certs/magistrala-server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEYjCCA0oCFGXr7rfGAynaa4KMTG1+23EEF0lYMA0GCSqGSIb3DQEBCwUAMHUx 3 | IjAgBgNVBAMMGU1hZ2lzdHJhbGFfU2VsZl9TaWduZWRfQ0ExEzARBgNVBAoMCk1h 4 | Z2lzdHJhbGExFjAUBgNVBAsMDW1hZ2lzdHJhbGFfY2ExIjAgBgkqhkiG9w0BCQEW 5 | E2luZm9AbWFnaXN0cmFsYS5jb20wHhcNMjMxMDMwMDgxOTA4WhcNMjYwNzI2MDgx 6 | OTA4WjBmMRIwEAYDVQQDDAlsb2NhbGhvc3QxEzARBgNVBAoMCk1hZ2lzdHJhbGEx 7 | FzAVBgNVBAsMDm1hZ2lzdHJhbGFfY3J0MSIwIAYJKoZIhvcNAQkBFhNpbmZvQG1h 8 | Z2lzdHJhbGEuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAojas 9 | t6M294uS5q8oFmYM6DULVQ1lY3K659VusJshjGvn8bi50vhKo8PpxL6ygVpjWcHG 10 | +/gclQnTaYZumC1TUohibpBnrFx1PZUvGiryAPudFY2nC5af5BQnYGi845FcVWx5 11 | FNLq+IsedgSZf7FuGcZruXiukBCWVyWJRJh+8FDakc65BPeG9FpCxbeLZ1nrDpnQ 12 | bhHbwEQrwwHk0FHZ/3cuVFJAjwqJSivJ9598eU0YWAsqsLM3uYyvOMd8alMs5vCZ 13 | 9tMCpO2v6xTdJ6kr68SwQQAiefRy6gsD5J5A4ySyCz7KX9fHCrqx1kdcDJ/CXZmh 14 | mXxrCFKSjqjuSn2qtm+gxvAc26Zbt5z5eihpdISDUKrjW11+yapNZLATGBX8ktek 15 | gW467V9DQYOsbA3fNkWgd5UcV5HIViUpqFMFvi1NpWc2INi/PTDWuAIBLUiVNk0W 16 | qMtG7/HqFRPn6MrNGpvFpglgxXGNfjsggkK/3INtFnAou2rN9+ieeuzO7Zjrtwsq 17 | sP64GVw/vLv3tgT6TIZmDnCDCqtEGEVutt7ldu3M0/fLm4qOUsZqFGrIOO1cfI4x 18 | 7FRnHwaTsTB1Og+I7lEujb4efHV+uRjKyrGh6L6hDt94IkGm6ZEj5z/iEmq16jRX 19 | dUbYsu4f1KlfTYdHWGHp+6kAmDn0jGCwz2BBrnsCAwEAATANBgkqhkiG9w0BAQsF 20 | AAOCAQEAKyg5kvDk+TQ6ZDCK7qxKY+uN9setYvvsLfde+Uy51a3zj8RIHRgkOT2C 21 | LuuTtTYKu3XmfCKId0oTXynGuP+yDAIuVwuZz3S0VmA8ijoZ87LJXzsLjjTjQSzZ 22 | ar6RmlRDH+8Bm4AOrT4TDupqifag4J0msHkNPo0jVK6fnuniqJoSlhIbbHrJTHhv 23 | jKNXrThjr/irgg1MZ7slojieOS0QoZHRE9eunIR5enDJwB5pWUJSmZWlisI7+Ibi 24 | 06+j8wZegU0nqeWp4wFSZxKnrzz5B5Qu9SrALwlHWirzBpyr0gAcF2v7nzbWviZ/ 25 | 0VMyY4FGEbkp6trMxwJs5hGYhAiyXg== 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /docker/ssl/dhparam.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DH PARAMETERS----- 2 | MIIBCAKCAQEAquN8NRcSdLOM9RiumqWH8Jw3CGVR/eQQeq+jvT3zpxlUQPAMExQb 3 | MRCspm1oRgDWGvch3Z4zfMmBZyzKJA4BDTh4USzcE5zvnx8aUcUPZPQpwSicKgzb 4 | QGnl0Xf/75GAWrwhxn8GNyMP29wrpcd1Qg8fEQ3HAW1fCd9girKMKY9aBaHli/h2 5 | R9Rd/KTbeqN88aoMjUvZHooIIZXu0A+kyulOajYQO4k3Sp6CBqv0FFcoLQnYNH13 6 | kMUE5qJ68U732HybTw8sofTCOxKcCfM2kVP7dVoF3prlGjUw3z3l3STY8vuTdq0B 7 | R7PslkoQHNmqcL+2gouoWP3GI+IeRzGSSwIBAg== 8 | -----END DH PARAMETERS----- 9 | -------------------------------------------------------------------------------- /docker/supermq-docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | FROM golang:1.24.3-alpine AS builder 5 | ARG SVC 6 | ARG GOARCH 7 | ARG GOARM 8 | ARG VERSION 9 | ARG COMMIT 10 | ARG TIME 11 | 12 | WORKDIR /go/src/github.com/absmach/supermq 13 | COPY . . 14 | RUN apk update \ 15 | && apk add make upx\ 16 | && make $SVC \ 17 | && upx build/$SVC \ 18 | && mv build/$SVC /exe 19 | 20 | FROM scratch 21 | # Certificates are needed so that mailing util can work. 22 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 23 | COPY --from=builder /exe / 24 | ENTRYPOINT ["/exe"] 25 | -------------------------------------------------------------------------------- /docker/supermq-docker/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | FROM scratch 5 | ARG SVC 6 | COPY $SVC /exe 7 | COPY --from=alpine:latest /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 8 | ENTRYPOINT ["/exe"] 9 | -------------------------------------------------------------------------------- /docker/supermq-docker/addons/certs/config.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | common_name: "AbstractMachines_Selfsigned_ca" 5 | organization: 6 | - "AbstractMacines" 7 | organizational_unit: 8 | - "AbstractMachines_ca" 9 | country: 10 | - "France" 11 | province: 12 | - "Paris" 13 | locality: 14 | - "Quai de Valmy" 15 | postal_code: 16 | - "75010 Paris" 17 | dns_names: 18 | - "localhost" 19 | ip_addresses: 20 | - "localhost" 21 | -------------------------------------------------------------------------------- /docker/supermq-docker/addons/prometheus/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # This docker-compose file contains optional Prometheus and Grafana service for SuperMQ platform. 5 | # Since this service is optional, this file is dependent of docker-compose.yaml file 6 | # from <project_root>/docker. In order to run this service, execute command: 7 | # docker compose -f docker/addons/prometheus/docker-compose.yaml up 8 | # from project root. 9 | 10 | networks: 11 | supermq-base-net: 12 | name: supermq-base-net 13 | external: true 14 | 15 | volumes: 16 | supermq-prometheus-volume: 17 | 18 | 19 | services: 20 | promethues: 21 | image: prom/prometheus:v2.49.1 22 | container_name: supermq-prometheus 23 | restart: on-failure 24 | ports: 25 | - ${SMQ_PROMETHEUS_PORT}:${SMQ_PROMETHEUS_PORT} 26 | networks: 27 | - supermq-base-net 28 | volumes: 29 | - type: bind 30 | source: ./metrics/prometheus.yaml 31 | target: /etc/prometheus/prometheus.yaml 32 | - supermq-prometheus-volume:/prometheus 33 | 34 | grafana: 35 | image: grafana/grafana:10.2.3 36 | container_name: supermq-grafana 37 | depends_on: 38 | - promethues 39 | restart: on-failure 40 | ports: 41 | - ${SMQ_GRAFANA_PORT}:${SMQ_GRAFANA_PORT} 42 | environment: 43 | - GF_SECURITY_ADMIN_USER=${SMQ_GRAFANA_ADMIN_USER} 44 | - GF_SECURITY_ADMIN_PASSWORD=${SMQ_GRAFANA_ADMIN_PASSWORD} 45 | networks: 46 | - supermq-base-net 47 | volumes: 48 | - type: bind 49 | source: ./grafana/datasource.yaml 50 | target: /etc/grafana/provisioning/datasources/datasource.yaml 51 | - type: bind 52 | source: ./grafana/dashboard.yaml 53 | target: /etc/grafana/provisioning/dashboards/main.yaml 54 | - type: bind 55 | source: ./grafana/example-dashboard.json 56 | target: /var/lib/grafana/dashboards/example-dashboard.json 57 | -------------------------------------------------------------------------------- /docker/supermq-docker/addons/prometheus/grafana/dashboard.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | apiVersion: 1 5 | 6 | providers: 7 | - name: "Dashboard provider" 8 | orgId: 1 9 | type: file 10 | disableDeletion: false 11 | updateIntervalSeconds: 10 12 | allowUiUpdates: false 13 | options: 14 | path: /var/lib/grafana/dashboards 15 | foldersFromFilesStructure: true 16 | -------------------------------------------------------------------------------- /docker/supermq-docker/addons/prometheus/grafana/datasource.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | apiVersion: 1 5 | 6 | datasources: 7 | - name: Prometheus 8 | type: prometheus 9 | url: http://supermq-prometheus:9090 10 | isDefault: true 11 | access: proxy 12 | editable: true 13 | -------------------------------------------------------------------------------- /docker/supermq-docker/addons/prometheus/metrics/prometheus.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | global: 5 | scrape_interval: 15s 6 | evaluation_interval: 15s 7 | 8 | scrape_configs: 9 | - job_name: 'supermq' 10 | honor_timestamps: true 11 | scrape_interval: 15s 12 | scrape_timeout: 10s 13 | metrics_path: /metrics 14 | follow_redirects: true 15 | enable_http2: true 16 | static_configs: 17 | - targets: 18 | - supermq-clients:9000 19 | - supermq-users:9002 20 | - supermq-http:8008 21 | - supermq-ws:8186 22 | - supermq-coap:5683 23 | -------------------------------------------------------------------------------- /docker/supermq-docker/addons/vault/config.hcl: -------------------------------------------------------------------------------- 1 | storage "file" { 2 | path = "/vault/file" 3 | } 4 | 5 | listener "tcp" { 6 | address = "0.0.0.0:8200" 7 | tls_disable = 1 8 | } 9 | 10 | ui = true 11 | -------------------------------------------------------------------------------- /docker/supermq-docker/addons/vault/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # This docker-compose file contains optional Vault service for SuperMQ platform. 5 | # Since this is optional, this file is dependent of docker-compose file 6 | # from <project_root>/docker. In order to run these services, execute command: 7 | # docker compose -f docker/docker-compose.yaml -f docker/addons/vault/docker-compose.yaml up 8 | # from project root. Vault default port (8200) is exposed, so you can use Vault CLI tool for 9 | # vault inspection and administration, as well as access the UI. 10 | 11 | networks: 12 | supermq-base-net: 13 | name: supermq-base-net 14 | external: true 15 | 16 | volumes: 17 | supermq-vault-volume: 18 | 19 | 20 | services: 21 | vault: 22 | image: hashicorp/vault:1.15.4 23 | container_name: supermq-vault 24 | ports: 25 | - ${SMQ_VAULT_PORT}:8200 26 | networks: 27 | - supermq-base-net 28 | volumes: 29 | - supermq-vault-volume:/vault/file 30 | - supermq-vault-volume:/vault/logs 31 | - ./config.hcl:/vault/config/config.hcl 32 | - ./entrypoint.sh:/entrypoint.sh 33 | environment: 34 | VAULT_ADDR: http://127.0.0.1:${SMQ_VAULT_PORT} 35 | SMQ_VAULT_PORT: ${SMQ_VAULT_PORT} 36 | SMQ_VAULT_UNSEAL_KEY_1: ${SMQ_VAULT_UNSEAL_KEY_1} 37 | SMQ_VAULT_UNSEAL_KEY_2: ${SMQ_VAULT_UNSEAL_KEY_2} 38 | SMQ_VAULT_UNSEAL_KEY_3: ${SMQ_VAULT_UNSEAL_KEY_3} 39 | entrypoint: /bin/sh 40 | command: /entrypoint.sh 41 | cap_add: 42 | - IPC_LOCK 43 | -------------------------------------------------------------------------------- /docker/supermq-docker/addons/vault/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/dumb-init /bin/sh 2 | # Copyright (c) Abstract Machines 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | VAULT_CONFIG_DIR=/vault/config 6 | 7 | docker-entrypoint.sh server & 8 | VAULT_PID=$! 9 | 10 | sleep 2 11 | 12 | echo $SMQ_VAULT_UNSEAL_KEY_1 13 | echo $SMQ_VAULT_UNSEAL_KEY_2 14 | echo $SMQ_VAULT_UNSEAL_KEY_3 15 | 16 | if [[ ! -z "${SMQ_VAULT_UNSEAL_KEY_1}" ]] && 17 | [[ ! -z "${SMQ_VAULT_UNSEAL_KEY_2}" ]] && 18 | [[ ! -z "${SMQ_VAULT_UNSEAL_KEY_3}" ]]; then 19 | echo "Unsealing Vault" 20 | vault operator unseal ${SMQ_VAULT_UNSEAL_KEY_1} 21 | vault operator unseal ${SMQ_VAULT_UNSEAL_KEY_2} 22 | vault operator unseal ${SMQ_VAULT_UNSEAL_KEY_3} 23 | fi 24 | 25 | wait $VAULT_PID -------------------------------------------------------------------------------- /docker/supermq-docker/addons/vault/scripts/.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | data 5 | supermq_clients_certs_issue.hcl 6 | -------------------------------------------------------------------------------- /docker/supermq-docker/addons/vault/scripts/supermq_clients_certs_issue.template.hcl: -------------------------------------------------------------------------------- 1 | 2 | # Allow issue certificate with role with default issuer from Intermediate PKI 3 | path "${SMQ_VAULT_PKI_INT_PATH}/issue/${SMQ_VAULT_PKI_INT_CLIENTS_CERTS_ROLE_NAME}" { 4 | capabilities = ["create", "update"] 5 | } 6 | 7 | ## Revole certificate from Intermediate PKI 8 | path "${SMQ_VAULT_PKI_INT_PATH}/revoke" { 9 | capabilities = ["create", "update"] 10 | } 11 | 12 | ## List Revoked Certificates from Intermediate PKI 13 | path "${SMQ_VAULT_PKI_INT_PATH}/certs/revoked" { 14 | capabilities = ["list"] 15 | } 16 | 17 | 18 | ## List Certificates from Intermediate PKI 19 | path "${SMQ_VAULT_PKI_INT_PATH}/certs" { 20 | capabilities = ["list"] 21 | } 22 | 23 | ## Read Certificate from Intermediate PKI 24 | path "${SMQ_VAULT_PKI_INT_PATH}/cert/+" { 25 | capabilities = ["read"] 26 | } 27 | path "${SMQ_VAULT_PKI_INT_PATH}/cert/+/raw" { 28 | capabilities = ["read"] 29 | } 30 | path "${SMQ_VAULT_PKI_INT_PATH}/cert/+/raw/pem" { 31 | capabilities = ["read"] 32 | } 33 | -------------------------------------------------------------------------------- /docker/supermq-docker/addons/vault/scripts/vault_cmd.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | # Copyright (c) Abstract Machines 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | vault() { 6 | if is_container_running "supermq-vault"; then 7 | docker exec -it supermq-vault vault "$@" 8 | else 9 | if which vault &> /dev/null; then 10 | $(which vault) "$@" 11 | else 12 | echo "supermq-vault container or vault command not found. Please refer to the documentation: https://github.com/absmach/supermq/blob/main/docker/addons/vault/README.md" 13 | fi 14 | fi 15 | } 16 | 17 | is_container_running() { 18 | local container_name="$1" 19 | if [ "$(docker inspect --format '{{.State.Running}}' "$container_name" 2>/dev/null)" = "true" ]; then 20 | return 0 21 | else 22 | return 1 23 | fi 24 | } 25 | -------------------------------------------------------------------------------- /docker/supermq-docker/addons/vault/scripts/vault_copy_env.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | # Copyright (c) Abstract Machines 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | set -euo pipefail 6 | 7 | scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" 8 | 9 | # default env file path 10 | env_file="docker/.env" 11 | 12 | while [[ "$#" -gt 0 ]]; do 13 | case $1 in 14 | --env-file) 15 | if [[ -z "${2:-}" ]]; then 16 | echo "Error: --env-file requires a non-empty option argument." 17 | exit 1 18 | fi 19 | env_file="$2" 20 | if [[ ! -f "$env_file" ]]; then 21 | echo "Error: .env file not found at $env_file" 22 | exit 1 23 | fi 24 | shift 25 | ;; 26 | *) 27 | echo "Unknown parameter passed: $1" 28 | exit 1 29 | ;; 30 | esac 31 | shift 32 | done 33 | 34 | write_env() { 35 | if [ -e "$scriptdir/data/secrets" ]; then 36 | if [[ "$(uname)" == "Darwin" ]]; then 37 | SED_OPT=(-i '') 38 | else 39 | SED_OPT=(-i) 40 | fi 41 | 42 | sed "${SED_OPT[@]}" "s,SMQ_VAULT_UNSEAL_KEY_1=.*,SMQ_VAULT_UNSEAL_KEY_1=$(awk -F ': ' '$1 == "Unseal Key 1" {print $2}' "$scriptdir/data/secrets")," "$env_file" 43 | sed "${SED_OPT[@]}" "s,SMQ_VAULT_UNSEAL_KEY_2=.*,SMQ_VAULT_UNSEAL_KEY_2=$(awk -F ': ' '$1 == "Unseal Key 2" {print $2}' "$scriptdir/data/secrets")," "$env_file" 44 | sed "${SED_OPT[@]}" "s,SMQ_VAULT_UNSEAL_KEY_3=.*,SMQ_VAULT_UNSEAL_KEY_3=$(awk -F ': ' '$1 == "Unseal Key 3" {print $2}' "$scriptdir/data/secrets")," "$env_file" 45 | sed "${SED_OPT[@]}" "s,SMQ_VAULT_TOKEN=.*,SMQ_VAULT_TOKEN=$(awk -F ': ' '$1 == "Initial Root Token" {print $2}' "$scriptdir/data/secrets")," "$env_file" 46 | echo "Vault environment variables are set successfully in $env_file" 47 | else 48 | echo "Error: Source file '$scriptdir/data/secrets' not found." 49 | fi 50 | } 51 | 52 | write_env 53 | -------------------------------------------------------------------------------- /docker/supermq-docker/addons/vault/scripts/vault_init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | # Copyright (c) Abstract Machines 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | set -euo pipefail 6 | 7 | scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" 8 | 9 | # default env file path 10 | env_file="docker/.env" 11 | 12 | while [[ "$#" -gt 0 ]]; do 13 | case $1 in 14 | --env-file) 15 | if [[ -z "${2:-}" ]]; then 16 | echo "Error: --env-file requires a non-empty option argument." 17 | exit 1 18 | fi 19 | env_file="$2" 20 | if [[ ! -f "$env_file" ]]; then 21 | echo "Error: .env file not found at $env_file" 22 | exit 1 23 | fi 24 | shift 25 | ;; 26 | *) 27 | echo "Unknown parameter passed: $1" 28 | exit 1 29 | ;; 30 | esac 31 | shift 32 | done 33 | 34 | readDotEnv() { 35 | set -o allexport 36 | source "$env_file" 37 | set +o allexport 38 | } 39 | 40 | source "$scriptdir/vault_cmd.sh" 41 | 42 | readDotEnv 43 | 44 | mkdir -p "$scriptdir/data" 45 | 46 | vault operator init -address="$SMQ_VAULT_ADDR" 2>&1 | tee >(sed -r 's/\x1b\[[0-9;]*m//g' > "$scriptdir/data/secrets") 47 | -------------------------------------------------------------------------------- /docker/supermq-docker/addons/vault/scripts/vault_unseal.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | # Copyright (c) Abstract Machines 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | set -euo pipefail 6 | 7 | scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" 8 | 9 | # default env file path 10 | env_file="docker/.env" 11 | 12 | while [[ "$#" -gt 0 ]]; do 13 | case $1 in 14 | --env-file) 15 | if [[ -z "${2:-}" ]]; then 16 | echo "Error: --env-file requires a non-empty option argument." 17 | exit 1 18 | fi 19 | env_file="$2" 20 | if [[ ! -f "$env_file" ]]; then 21 | echo "Error: .env file not found at $env_file" 22 | exit 1 23 | fi 24 | shift 25 | ;; 26 | *) 27 | echo "Unknown parameter passed: $1" 28 | exit 1 29 | ;; 30 | esac 31 | shift 32 | done 33 | 34 | readDotEnv() { 35 | set -o allexport 36 | source "$env_file" 37 | set +o allexport 38 | } 39 | 40 | source "$scriptdir/vault_cmd.sh" 41 | 42 | readDotEnv 43 | 44 | vault operator unseal -address=${SMQ_VAULT_ADDR} ${SMQ_VAULT_UNSEAL_KEY_1} 45 | vault operator unseal -address=${SMQ_VAULT_ADDR} ${SMQ_VAULT_UNSEAL_KEY_2} 46 | vault operator unseal -address=${SMQ_VAULT_ADDR} ${SMQ_VAULT_UNSEAL_KEY_3} 47 | -------------------------------------------------------------------------------- /docker/supermq-docker/nats/nats.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | server_name: "nats_internal_broker" 5 | max_payload: 1MB 6 | max_connections: 1M 7 | port: $SMQ_NATS_PORT 8 | http_port: $SMQ_NATS_HTTP_PORT 9 | trace: true 10 | 11 | jetstream { 12 | store_dir: "/data" 13 | cipher: "aes" 14 | key: $SMQ_NATS_JETSTREAM_KEY 15 | max_mem: 1G 16 | } 17 | 18 | mqtt { 19 | port: 1883 20 | max_ack_pending: 1 21 | } 22 | 23 | websocket { 24 | port: 8080 25 | 26 | no_tls: true 27 | } 28 | -------------------------------------------------------------------------------- /docker/supermq-docker/nginx/.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | snippets/mqtt-upstream.conf 5 | snippets/mqtt-ws-upstream.conf -------------------------------------------------------------------------------- /docker/supermq-docker/nginx/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/ash 2 | # Copyright (c) Abstract Machines 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | if [ -z "$SMQ_MQTT_CLUSTER" ] 6 | then 7 | envsubst '${SMQ_MQTT_ADAPTER_MQTT_PORT}' < /etc/nginx/snippets/mqtt-upstream-single.conf > /etc/nginx/snippets/mqtt-upstream.conf 8 | envsubst '${SMQ_MQTT_ADAPTER_WS_PORT}' < /etc/nginx/snippets/mqtt-ws-upstream-single.conf > /etc/nginx/snippets/mqtt-ws-upstream.conf 9 | else 10 | envsubst '${SMQ_MQTT_ADAPTER_MQTT_PORT}' < /etc/nginx/snippets/mqtt-upstream-cluster.conf > /etc/nginx/snippets/mqtt-upstream.conf 11 | envsubst '${SMQ_MQTT_ADAPTER_WS_PORT}' < /etc/nginx/snippets/mqtt-ws-upstream-cluster.conf > /etc/nginx/snippets/mqtt-ws-upstream.conf 12 | fi 13 | 14 | envsubst ' 15 | ${SMQ_NGINX_SERVER_NAME} 16 | ${SMQ_AUTH_HTTP_PORT} 17 | ${SMQ_DOMAINS_HTTP_PORT} 18 | ${SMQ_GROUPS_HTTP_PORT} 19 | ${SMQ_USERS_HTTP_PORT} 20 | ${SMQ_CLIENTS_HTTP_PORT} 21 | ${SMQ_CLIENTS_AUTH_HTTP_PORT} 22 | ${SMQ_CHANNELS_HTTP_PORT} 23 | ${SMQ_HTTP_ADAPTER_PORT} 24 | ${SMQ_NGINX_MQTT_PORT} 25 | ${SMQ_NGINX_MQTTS_PORT} 26 | ${SMQ_WS_ADAPTER_HTTP_PORT}' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf 27 | 28 | exec nginx -g "daemon off;" 29 | -------------------------------------------------------------------------------- /docker/supermq-docker/nginx/snippets/http_access_log.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | log_format access_log_format 'HTTP/WS ' 5 | '$remote_addr: ' 6 | '"$request" $status; ' 7 | 'request time=$request_time ' 8 | 'upstream connect time=$upstream_connect_time ' 9 | 'upstream address $upstream_addr ' 10 | 'upstream status $upstream_status ' 11 | 'upstream response time=$upstream_response_time'; 12 | access_log access.log access_log_format; 13 | -------------------------------------------------------------------------------- /docker/supermq-docker/nginx/snippets/mqtt-upstream-cluster.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | upstream mqtt_cluster { 5 | least_conn; 6 | server mqtt-adapter-1:${SMQ_MQTT_ADAPTER_MQTT_PORT}; 7 | server mqtt-adapter-2:${SMQ_MQTT_ADAPTER_MQTT_PORT}; 8 | server mqtt-adapter-3:${SMQ_MQTT_ADAPTER_MQTT_PORT}; 9 | } 10 | -------------------------------------------------------------------------------- /docker/supermq-docker/nginx/snippets/mqtt-upstream-single.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | upstream mqtt_cluster { 5 | server mqtt-adapter:${SMQ_MQTT_ADAPTER_MQTT_PORT}; 6 | } 7 | -------------------------------------------------------------------------------- /docker/supermq-docker/nginx/snippets/mqtt-ws-upstream-cluster.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | upstream mqtt_ws_cluster { 5 | least_conn; 6 | server mqtt-adapter-1:${SMQ_MQTT_ADAPTER_WS_PORT}; 7 | server mqtt-adapter-2:${SMQ_MQTT_ADAPTER_WS_PORT}; 8 | server mqtt-adapter-3:${SMQ_MQTT_ADAPTER_WS_PORT}; 9 | } 10 | -------------------------------------------------------------------------------- /docker/supermq-docker/nginx/snippets/mqtt-ws-upstream-single.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | upstream mqtt_ws_cluster { 5 | server mqtt-adapter:${SMQ_MQTT_ADAPTER_WS_PORT}; 6 | } 7 | -------------------------------------------------------------------------------- /docker/supermq-docker/nginx/snippets/proxy-headers.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | proxy_redirect off; 5 | proxy_set_header Host $host; 6 | proxy_set_header X-Real-IP $remote_addr; 7 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 8 | proxy_set_header X-Forwarded-Proto $scheme; 9 | 10 | # Allow OPTIONS method CORS 11 | if ($request_method = OPTIONS) { 12 | add_header Content-Length 0; 13 | add_header Content-Type text/plain; 14 | return 200; 15 | } 16 | -------------------------------------------------------------------------------- /docker/supermq-docker/nginx/snippets/ssl-client.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | ssl_client_certificate /etc/ssl/certs/ca.crt; 5 | ssl_verify_depth 2; 6 | -------------------------------------------------------------------------------- /docker/supermq-docker/nginx/snippets/ssl.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # These paths are set to its default values as 5 | # a volume in the docker/docker-compose.yaml file. 6 | ssl_certificate /etc/ssl/certs/supermq-server.crt; 7 | ssl_certificate_key /etc/ssl/private/supermq-server.key; 8 | ssl_dhparam /etc/ssl/certs/dhparam.pem; 9 | 10 | ssl_protocols TLSv1.2 TLSv1.3; 11 | ssl_prefer_server_ciphers on; 12 | ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; 13 | ssl_ecdh_curve secp384r1; 14 | ssl_session_tickets off; 15 | resolver 8.8.8.8 8.8.4.4 valid=300s; 16 | resolver_timeout 5s; 17 | -------------------------------------------------------------------------------- /docker/supermq-docker/nginx/snippets/stream_access_log.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | log_format access_log_format '$protocol ' 5 | '$remote_addr: ' 6 | 'status=$status; upstream connect time=$upstream_connect_time'; 7 | access_log access.log access_log_format; 8 | -------------------------------------------------------------------------------- /docker/supermq-docker/nginx/snippets/verify-ssl-client.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | if ($ssl_client_verify != SUCCESS) { 5 | return 403; 6 | } 7 | if ($auth_key = '') { 8 | return 403; 9 | } 10 | -------------------------------------------------------------------------------- /docker/supermq-docker/nginx/snippets/ws-upgrade.conf: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | proxy_http_version 1.1; 5 | proxy_set_header Upgrade $http_upgrade; 6 | proxy_set_header Connection "Upgrade"; 7 | proxy_connect_timeout 7d; 8 | proxy_send_timeout 7d; 9 | proxy_read_timeout 7d; 10 | -------------------------------------------------------------------------------- /docker/supermq-docker/rabbitmq/enabled_plugins: -------------------------------------------------------------------------------- 1 | [rabbitmq_management,rabbitmq_mqtt,rabbitmq_web_mqtt]. 2 | -------------------------------------------------------------------------------- /docker/supermq-docker/rabbitmq/rabbitmq.conf: -------------------------------------------------------------------------------- 1 | ## DEFAULT SETTINGS ARE NOT MEANT TO BE TAKEN STRAIGHT INTO PRODUCTION 2 | ## see https://www.rabbitmq.com/configure.html for further information 3 | ## on configuring RabbitMQ 4 | 5 | ## allow access to the guest user from anywhere on the network 6 | ## https://www.rabbitmq.com/access-control.html#loopback-users 7 | ## https://www.rabbitmq.com/production-checklist.html#users 8 | loopback_users.guest = false 9 | 10 | ## Send all logs to stdout/TTY. Necessary to see logs when running via 11 | ## a container 12 | log.console = true 13 | 14 | ## Enable anonymous connection 15 | mqtt.allow_anonymous = true 16 | -------------------------------------------------------------------------------- /docker/supermq-docker/ssl/.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | *grpc-server* 5 | *grpc-client* 6 | *srl 7 | *conf 8 | -------------------------------------------------------------------------------- /docker/supermq-docker/ssl/certs/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDyzCCArOgAwIBAgIUDIJg63dQVzoD9nmWi9YPscQwTgIwDQYJKoZIhvcNAQEN 3 | BQAwdTEiMCAGA1UEAwwZTWFnaXN0cmFsYV9TZWxmX1NpZ25lZF9DQTETMBEGA1UE 4 | CgwKTWFnaXN0cmFsYTEWMBQGA1UECwwNbWFnaXN0cmFsYV9jYTEiMCAGCSqGSIb3 5 | DQEJARYTaW5mb0BtYWdpc3RyYWxhLmNvbTAeFw0yMzEwMzAwODE5MDFaFw0yNjEw 6 | MjkwODE5MDFaMHUxIjAgBgNVBAMMGU1hZ2lzdHJhbGFfU2VsZl9TaWduZWRfQ0Ex 7 | EzARBgNVBAoMCk1hZ2lzdHJhbGExFjAUBgNVBAsMDW1hZ2lzdHJhbGFfY2ExIjAg 8 | BgkqhkiG9w0BCQEWE2luZm9AbWFnaXN0cmFsYS5jb20wggEiMA0GCSqGSIb3DQEB 9 | AQUAA4IBDwAwggEKAoIBAQCWNIeGfo/SePOvviJE6UHJhBzWcPfNVbzSF6A42WgB 10 | DEgI3KFr+/rgWMEaCOD4QzCl3Lqa89EgCA7xCgxcqFwEo33SyhAivwoHL2pRVHXn 11 | oee3z9U757T63YLE0qrXQY2cbyChX/OU99rZxyd5l5jUGN7MCu+RYurfTIiYN+Uv 12 | NZdl8a3X84g7fa70EOYas7cTunWUt9x64/jYDoYmn+XPXET1yEU1dQTnKY4cRjhv 13 | HS1u2QsadHKi1hgeILyLbB4u1T5N+WfxFknhFHTu8PVPxfowrVv/xzmxOe0zSZFd 14 | SbhtrmwT4S1wJ4PfUa3+tYZVtjEKKbyObsAW91WzOLS9AgMBAAGjUzBRMB0GA1Ud 15 | DgQWBBQkE4koZctEZpTz9pq6a6s6xg+myTAfBgNVHSMEGDAWgBQkE4koZctEZpTz 16 | 9pq6a6s6xg+myTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IBAQA7 17 | w/oh5U9loJsigf3X3T3jQM8PVmhsUfNMJ3kc1Yumr72S4sGKjdWwuU0vk+B3eQzh 18 | zXAj65BHhs1pXcukeoLR7YcHABEsEMg6lar/E4A+MgAZfZFVSvPpsByIK8I5ARk+ 19 | K1V/lWso+GJJM/lImPPnpvUWBdbntqC5WtjoMMGL9uyV3kVS6yT/kJ2ercnPzhPh 20 | uBkL1ZH3ivDn/0JDY+T8Sfeq08vNWaTcoC7qpPwqXhuT0ytY7oaBS5wmPcvvzpZg 21 | 6zZYPZfhjhdEFYY1hDrrPYNYO72jncUnwQVp3X0DQpSvbxp681hVkcEtwHB2B8l0 22 | tBGhgoH+TqZs0AUjoXM0 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /docker/supermq-docker/ssl/certs/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCWNIeGfo/SePOv 3 | viJE6UHJhBzWcPfNVbzSF6A42WgBDEgI3KFr+/rgWMEaCOD4QzCl3Lqa89EgCA7x 4 | CgxcqFwEo33SyhAivwoHL2pRVHXnoee3z9U757T63YLE0qrXQY2cbyChX/OU99rZ 5 | xyd5l5jUGN7MCu+RYurfTIiYN+UvNZdl8a3X84g7fa70EOYas7cTunWUt9x64/jY 6 | DoYmn+XPXET1yEU1dQTnKY4cRjhvHS1u2QsadHKi1hgeILyLbB4u1T5N+WfxFknh 7 | FHTu8PVPxfowrVv/xzmxOe0zSZFdSbhtrmwT4S1wJ4PfUa3+tYZVtjEKKbyObsAW 8 | 91WzOLS9AgMBAAECggEAEOxEq6jFO/WgIPgHROPR42ok1J1AMgx7nGEIjnciImIX 9 | mJYBAtlOM+oUAYKoFBh/2eQTSyN2t4jo5AvZhjP6wBQKeE4HQN7supADRrwBF7KU 10 | WI+MKvZpW81KrzG8CUoLsikMEFpu52UAbYJkZmznzVeq/GqsAKGYLEXjauD7S5Tu 11 | GeGVKO4novus6t3AHnBvfalIQ1JUuJFvcd5ZDhPljlzPbbWdM4WpRPaFZIKmfXft 12 | G7Izt58yPCYwhxohjrunRudyX3oKvmCBUOBXC8HdHzND/dLxwlrVu7OjmXprmC6P 13 | 8ggNpjAPeO8Y6+EKGne1fETNsKgODY/lXGOwECY4eQKBgQDSGi3WuoT/+DecVeSF 14 | GfmavdGCQKOD0kdl7qCeQYAL+SPVz4157AtxZs3idapvlbrc7wvw4Ev1XT7ZmWUj 15 | Lc4/UAITR8EkkFRVbxt2PvV86AiQtmXFguTNEX5vTszRwZ2+eqijZga5niBkqyAi 16 | SRuTwR8WrDZau4mRNnF8bUl8dQKBgQC3BKYifRp4hHqBycHe9rSMZ8Xz+ZOy+IFA 17 | vYap1Az+e8KuqlmD9Kfpp2Mjba1+HL5WKeTJGpFE7bhvb/xMPJgbMgtQ/cw4uDJ/ 18 | fwv4m6arf76ebOhaZtkT1vD4NyiyB+z6xP0TRgQRr2Or98XBSvGAYDXIn5vL7fUg 19 | KrDF0ePuKQKBgDfaOcFRiDW7uJzYwI0ZoJ8gQufLYyyR4+UXEJ/BbdbA/mPCbyuw 20 | MkKNP8Ip4YsUVL6S1avNFKQ/i4uxGY/Gh4ORM1wIwTGFJMYpaTV/+yafUFeYBWoC 21 | J+zT77aLTiucuuB+HwKBBtylSps4WqyCntAikK8oTLLGFAYEYRrgup5ZAoGAbQ8j 22 | JNghxwFCs0aT9ZZTfnt0NW9auUJmWzrVHSxUVe1P1J+EWiKXUJ/DbuAzizv7nAK4 23 | 57GiMU3rItS7pn5RMZt/rNKgOIhi5yDA9HNkPTwRTfyd9QjmgHEMBQ1xfa1FZSWv 24 | nSWS1SsLnPU37XgIMzShuByMTVhOQs3NqwPo7AkCgYAf8AzQNjFCoTwU3SJezJ4H 25 | 9j1jvMO232hAl8UDNtqvJ1APn87tOtnfX48OMoRrP9kKI0oygE3pq7rFxu1qmTns 26 | Zir0+KLeWGg58fSZkUEAp6kbO5CKwoeVAY9EMgd7BYBqlXLqUNfdH0L+KUOFKHha 27 | 7e82VxpgBeskzAqN1e7YRA== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /docker/supermq-docker/ssl/certs/supermq-server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEYjCCA0oCFGXr7rfGAynaa4KMTG1+23EEF0lYMA0GCSqGSIb3DQEBCwUAMHUx 3 | IjAgBgNVBAMMGU1hZ2lzdHJhbGFfU2VsZl9TaWduZWRfQ0ExEzARBgNVBAoMCk1h 4 | Z2lzdHJhbGExFjAUBgNVBAsMDW1hZ2lzdHJhbGFfY2ExIjAgBgkqhkiG9w0BCQEW 5 | E2luZm9AbWFnaXN0cmFsYS5jb20wHhcNMjMxMDMwMDgxOTA4WhcNMjYwNzI2MDgx 6 | OTA4WjBmMRIwEAYDVQQDDAlsb2NhbGhvc3QxEzARBgNVBAoMCk1hZ2lzdHJhbGEx 7 | FzAVBgNVBAsMDm1hZ2lzdHJhbGFfY3J0MSIwIAYJKoZIhvcNAQkBFhNpbmZvQG1h 8 | Z2lzdHJhbGEuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAojas 9 | t6M294uS5q8oFmYM6DULVQ1lY3K659VusJshjGvn8bi50vhKo8PpxL6ygVpjWcHG 10 | +/gclQnTaYZumC1TUohibpBnrFx1PZUvGiryAPudFY2nC5af5BQnYGi845FcVWx5 11 | FNLq+IsedgSZf7FuGcZruXiukBCWVyWJRJh+8FDakc65BPeG9FpCxbeLZ1nrDpnQ 12 | bhHbwEQrwwHk0FHZ/3cuVFJAjwqJSivJ9598eU0YWAsqsLM3uYyvOMd8alMs5vCZ 13 | 9tMCpO2v6xTdJ6kr68SwQQAiefRy6gsD5J5A4ySyCz7KX9fHCrqx1kdcDJ/CXZmh 14 | mXxrCFKSjqjuSn2qtm+gxvAc26Zbt5z5eihpdISDUKrjW11+yapNZLATGBX8ktek 15 | gW467V9DQYOsbA3fNkWgd5UcV5HIViUpqFMFvi1NpWc2INi/PTDWuAIBLUiVNk0W 16 | qMtG7/HqFRPn6MrNGpvFpglgxXGNfjsggkK/3INtFnAou2rN9+ieeuzO7Zjrtwsq 17 | sP64GVw/vLv3tgT6TIZmDnCDCqtEGEVutt7ldu3M0/fLm4qOUsZqFGrIOO1cfI4x 18 | 7FRnHwaTsTB1Og+I7lEujb4efHV+uRjKyrGh6L6hDt94IkGm6ZEj5z/iEmq16jRX 19 | dUbYsu4f1KlfTYdHWGHp+6kAmDn0jGCwz2BBrnsCAwEAATANBgkqhkiG9w0BAQsF 20 | AAOCAQEAKyg5kvDk+TQ6ZDCK7qxKY+uN9setYvvsLfde+Uy51a3zj8RIHRgkOT2C 21 | LuuTtTYKu3XmfCKId0oTXynGuP+yDAIuVwuZz3S0VmA8ijoZ87LJXzsLjjTjQSzZ 22 | ar6RmlRDH+8Bm4AOrT4TDupqifag4J0msHkNPo0jVK6fnuniqJoSlhIbbHrJTHhv 23 | jKNXrThjr/irgg1MZ7slojieOS0QoZHRE9eunIR5enDJwB5pWUJSmZWlisI7+Ibi 24 | 06+j8wZegU0nqeWp4wFSZxKnrzz5B5Qu9SrALwlHWirzBpyr0gAcF2v7nzbWviZ/ 25 | 0VMyY4FGEbkp6trMxwJs5hGYhAiyXg== 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /docker/supermq-docker/ssl/dhparam.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DH PARAMETERS----- 2 | MIIBCAKCAQEAquN8NRcSdLOM9RiumqWH8Jw3CGVR/eQQeq+jvT3zpxlUQPAMExQb 3 | MRCspm1oRgDWGvch3Z4zfMmBZyzKJA4BDTh4USzcE5zvnx8aUcUPZPQpwSicKgzb 4 | QGnl0Xf/75GAWrwhxn8GNyMP29wrpcd1Qg8fEQ3HAW1fCd9girKMKY9aBaHli/h2 5 | R9Rd/KTbeqN88aoMjUvZHooIIZXu0A+kyulOajYQO4k3Sp6CBqv0FFcoLQnYNH13 6 | kMUE5qJ68U732HybTw8sofTCOxKcCfM2kVP7dVoF3prlGjUw3z3l3STY8vuTdq0B 7 | R7PslkoQHNmqcL+2gouoWP3GI+IeRzGSSwIBAg== 8 | -----END DH PARAMETERS----- 9 | -------------------------------------------------------------------------------- /docker/supermq-docker/templates/smtp-notifier.tmpl: -------------------------------------------------------------------------------- 1 | To: {{range $index, $v := .To}}{{if $index}},{{end}}{{$v}}{{end}} 2 | From: {{.From}} 3 | Subject: {{.Subject}} 4 | {{.Header}} 5 | You have a new message: 6 | {{.Content}} 7 | {{.Footer}} 8 | 9 | -------------------------------------------------------------------------------- /docker/supermq-docker/templates/users.tmpl: -------------------------------------------------------------------------------- 1 | Dear {{.User}}, 2 | 3 | We have received a request to reset your password for your account on {{.Host}}. To proceed with resetting your password, please click on the link below: 4 | 5 | {{.Content}} 6 | 7 | If you did not initiate this request, please disregard this message and your password will remain unchanged. 8 | 9 | Thank you for using {{.Host}}. 10 | 11 | Best regards, 12 | 13 | {{.Footer}} 14 | -------------------------------------------------------------------------------- /docker/templates/re.tmpl: -------------------------------------------------------------------------------- 1 | {{.Header}} 2 | {{.Content}} 3 | {{.Footer}} 4 | 5 | -------------------------------------------------------------------------------- /internal/clients/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package clients contains the domain concept definitions needed to support 5 | // Magistrala clients functionality for example: postgres, redis, grpc, jaeger. 6 | package clients 7 | -------------------------------------------------------------------------------- /internal/clients/redis/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package redis contains the domain concept definitions needed to support 5 | // Magistrala redis cache functionality. 6 | // 7 | // It provides the abstraction of the redis cache service, which is used 8 | // to configure, setup and connect to the redis cache. 9 | package redis 10 | -------------------------------------------------------------------------------- /internal/clients/redis/redis.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package redis 5 | 6 | import "github.com/redis/go-redis/v9" 7 | 8 | // Connect create new RedisDB client and connect to RedisDB server. 9 | func Connect(url string) (*redis.Client, error) { 10 | opts, err := redis.ParseURL(url) 11 | if err != nil { 12 | return nil, err 13 | } 14 | 15 | return redis.NewClient(opts), nil 16 | } 17 | -------------------------------------------------------------------------------- /internal/email/README.md: -------------------------------------------------------------------------------- 1 | # Magistrala Email Agent 2 | 3 | Magistrala Email Agent is used for sending emails. It wraps basic SMTP features and 4 | provides a simple API that Magistrala services can use to send email notifications. 5 | 6 | ## Configuration 7 | 8 | Magistrala Email Agent is configured using the following configuration parameters: 9 | 10 | | Parameter | Description | 11 | | ----------------------------------- | ----------------------------------------------------------------------- | 12 | | MG_EMAIL_HOST | Mail server host | 13 | | MG_EMAIL_PORT | Mail server port | 14 | | MG_EMAIL_USERNAME | Mail server username | 15 | | MG_EMAIL_PASSWORD | Mail server password | 16 | | MG_EMAIL_FROM_ADDRESS | Email "from" address | 17 | | MG_EMAIL_FROM_NAME | Email "from" name | 18 | | MG_EMAIL_TEMPLATE | Email template for sending notification emails | 19 | 20 | There are two authentication methods supported: Basic Auth and CRAM-MD5. 21 | If `MG_EMAIL_USERNAME` is empty, no authentication will be used. 22 | -------------------------------------------------------------------------------- /internal/email/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package email contains the domain concept definitions needed to support 5 | // Magistrala email functionality. 6 | package email 7 | -------------------------------------------------------------------------------- /internal/proto/readers/v1/readers.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | syntax = "proto3"; 5 | 6 | package readers.v1; 7 | 8 | option go_package = "github.com/absmach/magistrala/api/grpc/readers/v1"; 9 | 10 | // ReadersService is a service that provides access to 11 | // readers functionalities for Magistrala services. 12 | service ReadersService { 13 | rpc ReadMessages(ReadMessagesReq) 14 | returns (ReadMessagesRes) {} 15 | } 16 | 17 | message PageMetadata { 18 | uint64 limit = 1; 19 | uint64 offset = 2; 20 | string protocol = 3; 21 | string name = 4; 22 | double value = 5; 23 | string publisher = 6; 24 | bool bool_value = 7; 25 | string string_value = 8; 26 | string data_value = 9; 27 | double from = 10; 28 | double to = 11; 29 | string subtopic = 12; 30 | string interval = 13; 31 | bool read = 14; 32 | Aggregation aggregation = 15; 33 | string comparator = 16; 34 | string format = 17; 35 | } 36 | 37 | message ReadMessagesRes { 38 | uint64 total = 1; 39 | PageMetadata page_metadata = 2; 40 | repeated Message messages = 3; 41 | } 42 | 43 | message Message { 44 | oneof payload { 45 | SenMLMessage senml = 1; 46 | JsonMessage json = 2; 47 | } 48 | } 49 | 50 | message BaseMessage { 51 | string channel = 1; 52 | string subtopic = 2; 53 | string publisher = 3; 54 | string protocol = 4; 55 | } 56 | 57 | message SenMLMessage { 58 | BaseMessage base = 1; 59 | string name = 2; 60 | string unit = 3; 61 | double time = 4; 62 | double update_time = 5; 63 | optional double value = 6; 64 | optional string string_value = 7; 65 | optional string data_value = 8; 66 | optional bool bool_value = 9; 67 | optional double sum = 10; 68 | } 69 | 70 | message JsonMessage { 71 | BaseMessage base = 1; 72 | int64 created = 2; 73 | bytes payload = 3; 74 | } 75 | 76 | message ReadMessagesReq { 77 | string channel_id = 1; 78 | string domain_id = 2; 79 | PageMetadata page_metadata = 3; 80 | } 81 | 82 | // Aggregation defines supported data aggregations. 83 | enum Aggregation { 84 | AGGREGATION_UNSPECIFIED = 0; 85 | MAX = 1; 86 | MIN = 2; 87 | SUM = 3; 88 | COUNT = 4; 89 | AVG = 5; 90 | } 91 | -------------------------------------------------------------------------------- /internal/testsutil/common.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package testsutil 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | 10 | "github.com/absmach/supermq/pkg/uuid" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func GenerateUUID(t *testing.T) string { 15 | idProvider := uuid.New() 16 | ulid, err := idProvider.ID() 17 | require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) 18 | return ulid 19 | } 20 | -------------------------------------------------------------------------------- /pkg/README.md: -------------------------------------------------------------------------------- 1 | # Standalone packages 2 | 3 | The `pkg` directory (the current directory) contains a set of standalone packages that can be imported and used by external applications. The packages are specifically meant for the development of the Magistrala based back-end applications and implement common tasks needed by the programmatic operation of Magistrala platform. 4 | -------------------------------------------------------------------------------- /pkg/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package pkg contains library packages used by Magistrala services 5 | // and external services that integrate with Magistrala. 6 | package pkg 7 | -------------------------------------------------------------------------------- /pkg/errors/README.md: -------------------------------------------------------------------------------- 1 | # Errors 2 | 3 | `errors` package serve to build an arbitrary long error chain in order to capture errors returned from nested service calls. 4 | 5 | `errors` package contains the custom Go `error` interface implementation, `Error`. You use the `Error` interface to **wrap** two errors in a containing error as well as to test recursively if a given error **contains** some other error. 6 | -------------------------------------------------------------------------------- /pkg/errors/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package errors contains Magistrala errors definitions. 5 | package errors 6 | -------------------------------------------------------------------------------- /pkg/errors/repository/types.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package repository 5 | 6 | import "github.com/absmach/magistrala/pkg/errors" 7 | 8 | // Wrapper for Repository errors. 9 | var ( 10 | // ErrMalformedEntity indicates a malformed entity specification. 11 | ErrMalformedEntity = errors.New("malformed entity specification") 12 | 13 | // ErrNotFound indicates a non-existent entity request. 14 | ErrNotFound = errors.New("entity not found") 15 | 16 | // ErrConflict indicates that entity already exists. 17 | ErrConflict = errors.New("entity already exists") 18 | 19 | // ErrCreateEntity indicates error in creating entity or entities. 20 | ErrCreateEntity = errors.New("failed to create entity in the db") 21 | 22 | // ErrViewEntity indicates error in viewing entity or entities. 23 | ErrViewEntity = errors.New("view entity failed") 24 | 25 | // ErrUpdateEntity indicates error in updating entity or entities. 26 | ErrUpdateEntity = errors.New("update entity failed") 27 | 28 | // ErrRemoveEntity indicates error in removing entity. 29 | ErrRemoveEntity = errors.New("failed to remove entity") 30 | 31 | // ErrFailedOpDB indicates a failure in a database operation. 32 | ErrFailedOpDB = errors.New("operation on db element failed") 33 | 34 | // ErrFailedToRetrieveAllGroups failed to retrieve groups. 35 | ErrFailedToRetrieveAllGroups = errors.New("failed to retrieve all groups") 36 | 37 | // ErrMissingNames indicates missing first and last names. 38 | ErrMissingNames = errors.New("missing first or last name") 39 | ) 40 | -------------------------------------------------------------------------------- /pkg/errors/types.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package errors 5 | 6 | import "errors" 7 | 8 | var ( 9 | // ErrMalformedEntity indicates a malformed entity specification. 10 | ErrMalformedEntity = New("malformed entity specification") 11 | 12 | // ErrUnsupportedContentType indicates invalid content type. 13 | ErrUnsupportedContentType = errors.New("invalid content type") 14 | 15 | // ErrUnidentified indicates unidentified error. 16 | ErrUnidentified = errors.New("unidentified error") 17 | 18 | // ErrEmptyPath indicates empty file path. 19 | ErrEmptyPath = errors.New("empty file path") 20 | 21 | // ErrStatusAlreadyAssigned indicated that the client or group has already been assigned the status. 22 | ErrStatusAlreadyAssigned = errors.New("status already assigned") 23 | 24 | // ErrRollbackTx indicates failed to rollback transaction. 25 | ErrRollbackTx = errors.New("failed to rollback transaction") 26 | 27 | // ErrAuthentication indicates failure occurred while authenticating the entity. 28 | ErrAuthentication = errors.New("failed to perform authentication over the entity") 29 | 30 | // ErrAuthorization indicates failure occurred while authorizing the entity. 31 | ErrAuthorization = errors.New("failed to perform authorization over the entity") 32 | ) 33 | -------------------------------------------------------------------------------- /pkg/prometheus/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package prometheus provides a framework for defining and collecting metrics 5 | // for prometheus. 6 | package prometheus 7 | -------------------------------------------------------------------------------- /pkg/prometheus/metrics.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package prometheus 5 | 6 | import ( 7 | kitprometheus "github.com/go-kit/kit/metrics/prometheus" 8 | stdprometheus "github.com/prometheus/client_golang/prometheus" 9 | ) 10 | 11 | // MakeMetrics returns an instance of Prometheus implementations for metrics. 12 | // It returns a request counter and a request latency summary. 13 | // 14 | // counter, latency := metrics.MakeMetrics("demo-service", "api") 15 | func MakeMetrics(namespace, subsystem string) (*kitprometheus.Counter, *kitprometheus.Summary) { 16 | counter := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{ 17 | Namespace: namespace, 18 | Subsystem: subsystem, 19 | Name: "request_count", 20 | Help: "Number of requests received.", 21 | }, []string{"method"}) 22 | latency := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{ 23 | Namespace: namespace, 24 | Subsystem: subsystem, 25 | Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, 26 | Name: "request_latency_microseconds", 27 | Help: "Total duration of requests in microseconds.", 28 | }, []string{"method"}) 29 | 30 | return counter, latency 31 | } 32 | -------------------------------------------------------------------------------- /pkg/reltime/reltime.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package reltime 5 | 6 | import ( 7 | "fmt" 8 | "regexp" 9 | "strconv" 10 | "strings" 11 | "time" 12 | 13 | "github.com/absmach/magistrala/pkg/errors" 14 | ) 15 | 16 | var ( 17 | re = regexp.MustCompile(`(?i)^now\(\)([\+\-])(.+)$`) 18 | 19 | ErrInvalidDuration = errors.New("invalid duration format") 20 | ErrInvalidExpression = errors.New("invalid time expression") 21 | ErrUnsupportedUnit = errors.New("unsupported unit") 22 | ) 23 | 24 | func Parse(expr string) (time.Time, error) { 25 | now := time.Now() 26 | expr = strings.ReplaceAll(expr, " ", "") 27 | 28 | if strings.EqualFold(expr, "now()") { 29 | return now, nil 30 | } 31 | 32 | matches := re.FindStringSubmatch(expr) 33 | if len(matches) != 3 { 34 | return time.Time{}, errors.Wrap(ErrInvalidExpression, fmt.Errorf("%s", expr)) 35 | } 36 | 37 | sign := matches[1] 38 | durStr := matches[2] 39 | if strings.ContainsAny(durStr, "+-") { 40 | return time.Time{}, errors.Wrap(ErrInvalidExpression, fmt.Errorf("%s", expr)) 41 | } 42 | 43 | dur, err := parseComplexDuration(durStr) 44 | if err != nil { 45 | return time.Time{}, err 46 | } 47 | 48 | if sign == "-" { 49 | return now.Add(-dur), nil 50 | } 51 | return now.Add(dur), nil 52 | } 53 | 54 | func parseComplexDuration(s string) (time.Duration, error) { 55 | var total time.Duration 56 | re := regexp.MustCompile(`(\d+)([smhdwMY])`) 57 | matches := re.FindAllStringSubmatch(s, -1) 58 | 59 | if matches == nil { 60 | return 0, errors.Wrap(ErrInvalidDuration, fmt.Errorf("%s", s)) 61 | } 62 | 63 | for _, match := range matches { 64 | val, _ := strconv.Atoi(match[1]) 65 | unit := match[2] 66 | 67 | var d time.Duration 68 | switch unit { 69 | case "s": 70 | d = time.Duration(val) * time.Second 71 | case "m": 72 | d = time.Duration(val) * time.Minute 73 | case "h": 74 | d = time.Duration(val) * time.Hour 75 | case "d": 76 | d = time.Duration(val) * 24 * time.Hour 77 | case "w": 78 | d = time.Duration(val) * 7 * 24 * time.Hour 79 | default: 80 | return 0, errors.Wrap(ErrUnsupportedUnit, fmt.Errorf("%s", unit)) 81 | } 82 | 83 | total += d 84 | } 85 | return total, nil 86 | } 87 | -------------------------------------------------------------------------------- /pkg/reltime/reltime_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package reltime 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | "time" 10 | 11 | "github.com/absmach/magistrala/pkg/errors" 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestParse(t *testing.T) { 16 | now := time.Now() 17 | 18 | tests := []struct { 19 | desc string 20 | expr string 21 | expected time.Time 22 | err error 23 | }{ 24 | { 25 | desc: "testing expression now()-5d", 26 | expr: "now()-5d", 27 | expected: now.Add(-5 * 24 * time.Hour), 28 | err: nil, 29 | }, 30 | { 31 | desc: "testing expression now()+2h30m", 32 | expr: "now()+2h30m", 33 | expected: now.Add(2*time.Hour + 30*time.Minute), 34 | err: nil, 35 | }, 36 | { 37 | desc: "testing expression now()-1w3d10h40m", 38 | expr: "now()-1w3d10h40m", 39 | expected: now.Add(-(7*24+3*24+10)*time.Hour - 40*time.Minute), 40 | err: nil, 41 | }, 42 | { 43 | desc: "testing expression yesterday", 44 | expr: "yesterday", 45 | err: ErrInvalidExpression, 46 | }, 47 | { 48 | desc: "testing expression now()--5d", 49 | expr: "now()--5d", 50 | err: ErrInvalidExpression, 51 | }, 52 | { 53 | desc: "testing expression now()+", 54 | expr: "now()+", 55 | err: ErrInvalidExpression, 56 | }, 57 | { 58 | desc: "testing expression now()+5r", 59 | expr: "now()+5r", 60 | err: ErrInvalidDuration, 61 | }, 62 | { 63 | desc: "testing expression now()+5M", 64 | expr: "now()+5M", 65 | err: ErrUnsupportedUnit, 66 | }, 67 | } 68 | 69 | for _, tc := range tests { 70 | got, err := Parse(tc.expr) 71 | assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %v got %v and response time %v\n", tc.desc, tc.err, err, got)) 72 | if err == nil { 73 | assert.WithinDuration(t, tc.expected, got, time.Duration(10*time.Second)) 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /pkg/sdk/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package sdk contains Magistrala SDK. 5 | package sdk 6 | -------------------------------------------------------------------------------- /pkg/sdk/messages.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package sdk 5 | 6 | import ( 7 | "context" 8 | "encoding/json" 9 | "fmt" 10 | "net/http" 11 | "net/url" 12 | "strconv" 13 | "strings" 14 | 15 | "github.com/absmach/supermq/pkg/errors" 16 | ) 17 | 18 | const channelParts = 2 19 | 20 | func (sdk mgSDK) ReadMessages(ctx context.Context, pm MessagePageMetadata, chanName, domainID, token string) (MessagesPage, errors.SDKError) { 21 | chanNameParts := strings.SplitN(chanName, ".", channelParts) 22 | chanID := chanNameParts[0] 23 | subtopicPart := "" 24 | if len(chanNameParts) == channelParts { 25 | subtopicPart = fmt.Sprintf("?subtopic=%s", chanNameParts[1]) 26 | } 27 | 28 | msgURL, err := sdk.withMessageQueryParams(sdk.readersURL, fmt.Sprintf("%s/channels/%s/messages%s", domainID, chanID, subtopicPart), pm) 29 | if err != nil { 30 | return MessagesPage{}, errors.NewSDKError(err) 31 | } 32 | 33 | header := make(map[string]string) 34 | header["Content-Type"] = string(sdk.msgContentType) 35 | 36 | _, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, msgURL, token, nil, header, http.StatusOK) 37 | if sdkerr != nil { 38 | return MessagesPage{}, sdkerr 39 | } 40 | 41 | var mp MessagesPage 42 | if err := json.Unmarshal(body, &mp); err != nil { 43 | return MessagesPage{}, errors.NewSDKError(err) 44 | } 45 | 46 | return mp, nil 47 | } 48 | 49 | func (sdk mgSDK) withMessageQueryParams(baseURL, endpoint string, mpm MessagePageMetadata) (string, error) { 50 | b, err := json.Marshal(mpm) 51 | if err != nil { 52 | return "", err 53 | } 54 | q := map[string]interface{}{} 55 | if err := json.Unmarshal(b, &q); err != nil { 56 | return "", err 57 | } 58 | ret := url.Values{} 59 | for k, v := range q { 60 | switch t := v.(type) { 61 | case string: 62 | ret.Add(k, t) 63 | case float64: 64 | ret.Add(k, strconv.FormatFloat(t, 'f', -1, 64)) 65 | case uint64: 66 | ret.Add(k, strconv.FormatUint(t, 10)) 67 | case int64: 68 | ret.Add(k, strconv.FormatInt(t, 10)) 69 | case json.Number: 70 | ret.Add(k, t.String()) 71 | case bool: 72 | ret.Add(k, strconv.FormatBool(t)) 73 | } 74 | } 75 | qs := ret.Encode() 76 | 77 | return fmt.Sprintf("%s/%s?%s", baseURL, endpoint, qs), nil 78 | } 79 | -------------------------------------------------------------------------------- /pkg/sdk/responses.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package sdk 5 | 6 | import "github.com/absmach/supermq/pkg/transformers/senml" 7 | 8 | type PageRes struct { 9 | Total uint64 `json:"total"` 10 | Offset uint64 `json:"offset"` 11 | Limit uint64 `json:"limit"` 12 | } 13 | 14 | // bootstrapsPage contains list of bootstrap configs in a page with proper metadata. 15 | type BootstrapPage struct { 16 | Configs []BootstrapConfig `json:"configs"` 17 | PageRes 18 | } 19 | 20 | type SubscriptionPage struct { 21 | Subscriptions []Subscription `json:"subscriptions"` 22 | PageRes 23 | } 24 | 25 | type MessagesPage struct { 26 | Messages []senml.Message `json:"messages,omitempty"` 27 | PageRes 28 | } 29 | -------------------------------------------------------------------------------- /pkg/sdk/setup_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package sdk_test 5 | 6 | import ( 7 | "os" 8 | "testing" 9 | ) 10 | 11 | func TestMain(m *testing.M) { 12 | exitCode := m.Run() 13 | os.Exit(exitCode) 14 | } 15 | -------------------------------------------------------------------------------- /provision/api/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package api contains API-related concerns: endpoint definitions, middlewares 5 | // and all resource representations. 6 | package api 7 | -------------------------------------------------------------------------------- /provision/api/endpoint.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package api 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/absmach/magistrala/provision" 10 | apiutil "github.com/absmach/supermq/api/http/util" 11 | "github.com/absmach/supermq/pkg/errors" 12 | "github.com/go-kit/kit/endpoint" 13 | ) 14 | 15 | func doProvision(svc provision.Service) endpoint.Endpoint { 16 | return func(ctx context.Context, request interface{}) (interface{}, error) { 17 | req := request.(provisionReq) 18 | if err := req.validate(); err != nil { 19 | return nil, errors.Wrap(apiutil.ErrValidation, err) 20 | } 21 | 22 | res, err := svc.Provision(ctx, req.domainID, req.token, req.Name, req.ExternalID, req.ExternalKey) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | provisionResponse := provisionRes{ 28 | Clients: res.Clients, 29 | Channels: res.Channels, 30 | ClientCert: res.ClientCert, 31 | ClientKey: res.ClientKey, 32 | CACert: res.CACert, 33 | Whitelisted: res.Whitelisted, 34 | } 35 | 36 | return provisionResponse, nil 37 | } 38 | } 39 | 40 | func getMapping(svc provision.Service) endpoint.Endpoint { 41 | return func(ctx context.Context, request interface{}) (interface{}, error) { 42 | req := request.(mappingReq) 43 | if err := req.validate(); err != nil { 44 | return nil, errors.Wrap(apiutil.ErrValidation, err) 45 | } 46 | 47 | res, err := svc.Mapping(ctx, req.token) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | return mappingRes{Data: res}, nil 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /provision/api/logging.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build !test 5 | 6 | package api 7 | 8 | import ( 9 | "context" 10 | "log/slog" 11 | "time" 12 | 13 | "github.com/absmach/magistrala/provision" 14 | ) 15 | 16 | var _ provision.Service = (*loggingMiddleware)(nil) 17 | 18 | type loggingMiddleware struct { 19 | logger *slog.Logger 20 | svc provision.Service 21 | } 22 | 23 | // NewLoggingMiddleware adds logging facilities to the core service. 24 | func NewLoggingMiddleware(svc provision.Service, logger *slog.Logger) provision.Service { 25 | return &loggingMiddleware{logger, svc} 26 | } 27 | 28 | func (lm *loggingMiddleware) Provision(ctx context.Context, domainID, token, name, externalID, externalKey string) (res provision.Result, err error) { 29 | defer func(begin time.Time) { 30 | args := []any{ 31 | slog.String("duration", time.Since(begin).String()), 32 | slog.String("name", name), 33 | slog.String("external_id", externalID), 34 | } 35 | if err != nil { 36 | args = append(args, slog.Any("error", err)) 37 | lm.logger.Warn("Provision failed", args...) 38 | return 39 | } 40 | lm.logger.Info("Provision completed successfully", args...) 41 | }(time.Now()) 42 | 43 | return lm.svc.Provision(ctx, domainID, token, name, externalID, externalKey) 44 | } 45 | 46 | func (lm *loggingMiddleware) Cert(ctx context.Context, domainID, token, clientID, duration string) (cert, key string, err error) { 47 | defer func(begin time.Time) { 48 | args := []any{ 49 | slog.String("duration", time.Since(begin).String()), 50 | slog.String("client_id", clientID), 51 | slog.String("ttl", duration), 52 | } 53 | if err != nil { 54 | args = append(args, slog.Any("error", err)) 55 | lm.logger.Warn("Client certificate failed to create successfully", args...) 56 | return 57 | } 58 | lm.logger.Info("Client certificate created successfully", args...) 59 | }(time.Now()) 60 | 61 | return lm.svc.Cert(ctx, domainID, token, clientID, duration) 62 | } 63 | 64 | func (lm *loggingMiddleware) Mapping(ctx context.Context, token string) (res map[string]interface{}, err error) { 65 | defer func(begin time.Time) { 66 | args := []any{ 67 | slog.String("duration", time.Since(begin).String()), 68 | } 69 | if err != nil { 70 | args = append(args, slog.Any("error", err)) 71 | lm.logger.Warn("Mapping failed", args...) 72 | return 73 | } 74 | lm.logger.Info("Mapping completed successfully", args...) 75 | }(time.Now()) 76 | 77 | return lm.svc.Mapping(ctx, token) 78 | } 79 | -------------------------------------------------------------------------------- /provision/api/requests.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package api 5 | 6 | import apiutil "github.com/absmach/supermq/api/http/util" 7 | 8 | type provisionReq struct { 9 | token string 10 | domainID string 11 | Name string `json:"name"` 12 | ExternalID string `json:"external_id"` 13 | ExternalKey string `json:"external_key"` 14 | } 15 | 16 | func (req provisionReq) validate() error { 17 | if req.ExternalID == "" { 18 | return apiutil.ErrMissingID 19 | } 20 | if req.domainID == "" { 21 | return apiutil.ErrMissingDomainID 22 | } 23 | 24 | if req.ExternalKey == "" { 25 | return apiutil.ErrBearerKey 26 | } 27 | 28 | if req.Name == "" { 29 | return apiutil.ErrMissingName 30 | } 31 | 32 | return nil 33 | } 34 | 35 | type mappingReq struct { 36 | token string 37 | domainID string 38 | } 39 | 40 | func (req mappingReq) validate() error { 41 | if req.token == "" { 42 | return apiutil.ErrBearerToken 43 | } 44 | if req.domainID == "" { 45 | return apiutil.ErrMissingDomainID 46 | } 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /provision/api/responses.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package api 5 | 6 | import ( 7 | "encoding/json" 8 | "net/http" 9 | 10 | "github.com/absmach/supermq" 11 | sdk "github.com/absmach/supermq/pkg/sdk" 12 | ) 13 | 14 | var _ supermq.Response = (*provisionRes)(nil) 15 | 16 | type provisionRes struct { 17 | Clients []sdk.Client `json:"clients"` 18 | Channels []sdk.Channel `json:"channels"` 19 | ClientCert map[string]string `json:"client_cert,omitempty"` 20 | ClientKey map[string]string `json:"client_key,omitempty"` 21 | CACert string `json:"ca_cert,omitempty"` 22 | Whitelisted map[string]bool `json:"whitelisted,omitempty"` 23 | } 24 | 25 | func (res provisionRes) Code() int { 26 | return http.StatusCreated 27 | } 28 | 29 | func (res provisionRes) Headers() map[string]string { 30 | return map[string]string{} 31 | } 32 | 33 | func (res provisionRes) Empty() bool { 34 | return false 35 | } 36 | 37 | type mappingRes struct { 38 | Data interface{} 39 | } 40 | 41 | func (res mappingRes) Code() int { 42 | return http.StatusOK 43 | } 44 | 45 | func (res mappingRes) Headers() map[string]string { 46 | return map[string]string{} 47 | } 48 | 49 | func (res mappingRes) Empty() bool { 50 | return false 51 | } 52 | 53 | func (res mappingRes) MarshalJSON() ([]byte, error) { 54 | return json.Marshal(res.Data) 55 | } 56 | -------------------------------------------------------------------------------- /provision/api/transport.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package api 5 | 6 | import ( 7 | "context" 8 | "encoding/json" 9 | "log/slog" 10 | "net/http" 11 | 12 | "github.com/absmach/magistrala/provision" 13 | "github.com/absmach/supermq" 14 | api "github.com/absmach/supermq/api/http" 15 | apiutil "github.com/absmach/supermq/api/http/util" 16 | "github.com/absmach/supermq/pkg/errors" 17 | "github.com/go-chi/chi/v5" 18 | kithttp "github.com/go-kit/kit/transport/http" 19 | "github.com/prometheus/client_golang/prometheus/promhttp" 20 | ) 21 | 22 | const ( 23 | contentType = "application/json" 24 | ) 25 | 26 | // MakeHandler returns a HTTP handler for API endpoints. 27 | func MakeHandler(svc provision.Service, logger *slog.Logger, instanceID string) http.Handler { 28 | opts := []kithttp.ServerOption{ 29 | kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), 30 | } 31 | 32 | r := chi.NewRouter() 33 | 34 | r.Route("/{domainID}", func(r chi.Router) { 35 | r.Route("/mapping", func(r chi.Router) { 36 | r.Post("/", kithttp.NewServer( 37 | doProvision(svc), 38 | decodeProvisionRequest, 39 | api.EncodeResponse, 40 | opts..., 41 | ).ServeHTTP) 42 | r.Get("/", kithttp.NewServer( 43 | getMapping(svc), 44 | decodeMappingRequest, 45 | api.EncodeResponse, 46 | opts..., 47 | ).ServeHTTP) 48 | }) 49 | }) 50 | r.Handle("/metrics", promhttp.Handler()) 51 | r.Get("/health", supermq.Health("provision", instanceID)) 52 | 53 | return r 54 | } 55 | 56 | func decodeProvisionRequest(_ context.Context, r *http.Request) (interface{}, error) { 57 | if r.Header.Get("Content-Type") != contentType { 58 | return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) 59 | } 60 | 61 | req := provisionReq{ 62 | token: apiutil.ExtractBearerToken(r), 63 | domainID: chi.URLParam(r, "domainID"), 64 | } 65 | if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 66 | return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(err, errors.ErrMalformedEntity)) 67 | } 68 | 69 | return req, nil 70 | } 71 | 72 | func decodeMappingRequest(_ context.Context, r *http.Request) (interface{}, error) { 73 | if r.Header.Get("Content-Type") != contentType { 74 | return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType) 75 | } 76 | 77 | req := mappingReq{ 78 | token: apiutil.ExtractBearerToken(r), 79 | domainID: chi.URLParam(r, "domainID"), 80 | } 81 | 82 | return req, nil 83 | } 84 | -------------------------------------------------------------------------------- /provision/configs/config.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | file = "config.toml" 5 | 6 | [bootstrap] 7 | autowhite_list = true 8 | content = "" 9 | provision = true 10 | x509_provision = false 11 | 12 | 13 | [server] 14 | LogLevel = "info" 15 | ca_certs = "" 16 | http_port = "8190" 17 | mg_api_key = "" 18 | mg_bs_url = "http://localhost:9013" 19 | mg_certs_url = "http://localhost:9019" 20 | mg_pass = "" 21 | mg_user = "" 22 | mqtt_url = "" 23 | port = "" 24 | server_cert = "" 25 | server_key = "" 26 | clients_location = "http://localhost:9006" 27 | tls = true 28 | users_location = "" 29 | 30 | [[clients]] 31 | name = "client" 32 | 33 | [client.metadata] 34 | external_id = "xxxxxx" 35 | 36 | 37 | [[channels]] 38 | name = "control-channel" 39 | 40 | [channels.metadata] 41 | type = "control" 42 | 43 | [[channels]] 44 | name = "data-channel" 45 | 46 | [channels.metadata] 47 | type = "data" 48 | -------------------------------------------------------------------------------- /provision/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package provision contains domain concept definitions needed to support 5 | // Provision service feature, i.e. automate provision process. 6 | package provision 7 | -------------------------------------------------------------------------------- /re/aes.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package re 5 | 6 | import ( 7 | "crypto/aes" 8 | "crypto/cipher" 9 | "fmt" 10 | ) 11 | 12 | // encrypt implements AES CBC-128 ENCRYPTION which requires 3 data fields 13 | // 1. Key (16 bytes) 14 | // 2. Initialization Vector (IV) (16 bytes) 15 | // 3. Encrypted Data (16 bytes or length multiple a of 16) 16 | // The encrypted data is divided into blocks of 16 bytes (128 bits) which then operated on with the IV and Key. 17 | func encrypt(key []byte, iv []byte, data []byte) ([]byte, error) { 18 | block, err := aes.NewCipher(key) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | blockSize := block.BlockSize() 24 | if len(data)%blockSize != 0 { 25 | return nil, fmt.Errorf("payload length %d is not a multiple of AES block size %d", len(data), blockSize) 26 | } 27 | 28 | if len(iv) != blockSize { 29 | return nil, fmt.Errorf("size of the IV %d is not the same as block size %d", len(iv), blockSize) 30 | } 31 | 32 | mode := cipher.NewCBCEncrypter(block, iv) 33 | encrypted := make([]byte, len(data)) 34 | mode.CryptBlocks(encrypted, data) 35 | 36 | return encrypted, nil 37 | } 38 | 39 | // decrypt implements AES CBC-128 DECRYPTION which requires 3 data fields 40 | // 1. Key (16 bytes) 41 | // 2. Initialization Vector (IV) (16 bytes) 42 | // 3. Encrypted Data (16 bytes or length multiple a of 16) 43 | // The encrypted data is divided into blocks of 16 bytes (128 bits) which then operated on with the IV and Key. 44 | func decrypt(key []byte, iv []byte, encrypted []byte) ([]byte, error) { 45 | block, err := aes.NewCipher(key) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | blockSize := block.BlockSize() 51 | if len(encrypted)%blockSize != 0 { 52 | return nil, fmt.Errorf("encrypted payload length %d is not a multiple of AES block size %d", len(encrypted), blockSize) 53 | } 54 | 55 | if len(iv) != blockSize { 56 | return nil, fmt.Errorf("size of the IV %d is not the same as block size %d", len(iv), blockSize) 57 | } 58 | 59 | mode := cipher.NewCBCDecrypter(block, iv) 60 | decrypted := make([]byte, len(encrypted)) 61 | mode.CryptBlocks(decrypted, encrypted) 62 | 63 | return decrypted, nil 64 | } 65 | -------------------------------------------------------------------------------- /re/api/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package api contains API-related concerns: endpoint definitions, middlewares 5 | // and all resource representations. 6 | package api 7 | -------------------------------------------------------------------------------- /re/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package re contain the domain concept definitions needed to 5 | // support Magistrala Rule Egine services functionality. 6 | package re 7 | -------------------------------------------------------------------------------- /re/emailer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package re 5 | 6 | type Emailer interface { 7 | // SendEmailNotification sends an email to the recipients based on a trigger. 8 | SendEmailNotification(to []string, from, subject, header, user, content, footer string, attachments map[string][]byte) error 9 | } 10 | -------------------------------------------------------------------------------- /re/emailer/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package emailer contains the domain concept definitions needed to support 5 | // Magistrala re email service functionality. 6 | package emailer 7 | -------------------------------------------------------------------------------- /re/emailer/emailer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package emailer 5 | 6 | import ( 7 | "github.com/absmach/magistrala/internal/email" 8 | "github.com/absmach/magistrala/re" 9 | ) 10 | 11 | var _ re.Emailer = (*emailer)(nil) 12 | 13 | type emailer struct { 14 | agent *email.Agent 15 | } 16 | 17 | func New(a *email.Config) (re.Emailer, error) { 18 | e, err := email.New(a) 19 | return &emailer{agent: e}, err 20 | } 21 | 22 | func (e *emailer) SendEmailNotification(to []string, from, subject, header, user, content, footer string, attachments map[string][]byte) error { 23 | return e.agent.Send(to, from, subject, header, user, content, footer, attachments) 24 | } 25 | -------------------------------------------------------------------------------- /re/postgres/init.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package postgres 5 | 6 | import ( 7 | _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access 8 | migrate "github.com/rubenv/sql-migrate" 9 | ) 10 | 11 | func Migration() *migrate.MemoryMigrationSource { 12 | return &migrate.MemoryMigrationSource{ 13 | Migrations: []*migrate.Migration{ 14 | { 15 | Id: "rules_01", 16 | // VARCHAR(36) for colums with IDs as UUIDS have a maximum of 36 characters 17 | // STATUS 0 to imply enabled and 1 to imply disabled 18 | Up: []string{ 19 | `CREATE TABLE IF NOT EXISTS rules ( 20 | id VARCHAR(36) PRIMARY KEY, 21 | name VARCHAR(1024), 22 | domain_id VARCHAR(36) NOT NULL, 23 | metadata JSONB, 24 | created_by VARCHAR(254), 25 | created_at TIMESTAMP, 26 | updated_at TIMESTAMP, 27 | updated_by VARCHAR(254), 28 | input_channel VARCHAR(36), 29 | input_topic TEXT, 30 | output_channel VARCHAR(36), 31 | output_topic TEXT, 32 | status SMALLINT NOT NULL DEFAULT 0 CHECK (status >= 0), 33 | logic_type SMALLINT NOT NULL DEFAULT 0 CHECK (logic_type >= 0), 34 | logic_output SMALLINT[] NOT NULL DEFAULT '{}', 35 | logic_value BYTEA, 36 | time TIMESTAMP, 37 | recurring SMALLINT, 38 | recurring_period SMALLINT, 39 | start_datetime TIMESTAMP 40 | )`, 41 | }, 42 | Down: []string{ 43 | `DROP TABLE IF EXISTS rules`, 44 | }, 45 | }, 46 | { 47 | Id: "rules_02", 48 | Up: []string{ 49 | `CREATE TABLE IF NOT EXISTS report_config ( 50 | id VARCHAR(36) PRIMARY KEY, 51 | name VARCHAR(1024), 52 | description TEXT, 53 | domain_id VARCHAR(36) NOT NULL, 54 | status SMALLINT NOT NULL DEFAULT 0 CHECK (status >= 0), 55 | created_at TIMESTAMP, 56 | created_by VARCHAR(254), 57 | updated_at TIMESTAMP, 58 | updated_by VARCHAR(254), 59 | time TIMESTAMP, 60 | recurring SMALLINT, 61 | recurring_period SMALLINT, 62 | start_datetime TIMESTAMP, 63 | config JSONB, 64 | email JSONB, 65 | metrics JSONB 66 | );`, 67 | }, 68 | Down: []string{ 69 | `DROP TABLE IF EXISTS report_config;`, 70 | }, 71 | }, 72 | }, 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /re/status.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package re 5 | 6 | import ( 7 | "encoding/json" 8 | "strings" 9 | 10 | svcerr "github.com/absmach/supermq/pkg/errors/service" 11 | ) 12 | 13 | // Status represents Rule status. 14 | type Status uint8 15 | 16 | // Possible User status values. 17 | const ( 18 | // EnabledStatus represents enabled Rule. 19 | EnabledStatus Status = iota 20 | // DisabledStatus represents disabled Rule. 21 | DisabledStatus 22 | // DeletedStatus represents a rule that will be deleted. 23 | DeletedStatus 24 | 25 | // AllStatus is used for querying purposes to list rules irrespective 26 | // of their status - both enabled and disabled. It is never stored in the 27 | // database as the actual User status and should always be the largest 28 | // value in this enumeration. 29 | AllStatus 30 | ) 31 | 32 | // String representation of the possible status values. 33 | const ( 34 | Disabled = "disabled" 35 | Enabled = "enabled" 36 | Deleted = "deleted" 37 | All = "all" 38 | Unknown = "unknown" 39 | ) 40 | 41 | func (s Status) String() string { 42 | switch s { 43 | case DisabledStatus: 44 | return Disabled 45 | case EnabledStatus: 46 | return Enabled 47 | case DeletedStatus: 48 | return Deleted 49 | case AllStatus: 50 | return All 51 | default: 52 | return Unknown 53 | } 54 | } 55 | 56 | // ToStatus converts string value to a valid status. 57 | func ToStatus(status string) (Status, error) { 58 | switch status { 59 | case "", Enabled: 60 | return EnabledStatus, nil 61 | case Disabled: 62 | return DisabledStatus, nil 63 | case Deleted: 64 | return DeletedStatus, nil 65 | case All: 66 | return AllStatus, nil 67 | } 68 | return Status(0), svcerr.ErrInvalidStatus 69 | } 70 | 71 | func (s Status) MarshalJSON() ([]byte, error) { 72 | return json.Marshal(s.String()) 73 | } 74 | 75 | func (s *Status) UnmarshalJSON(data []byte) error { 76 | str := strings.Trim(string(data), "\"") 77 | val, err := ToStatus(str) 78 | *s = val 79 | return err 80 | } 81 | -------------------------------------------------------------------------------- /re/ticker.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package re 5 | 6 | import "time" 7 | 8 | type Ticker interface { 9 | Tick() <-chan time.Time 10 | Stop() 11 | } 12 | 13 | type timeTicker struct { 14 | *time.Ticker 15 | } 16 | 17 | func NewTicker(d time.Duration) Ticker { 18 | return &timeTicker{time.NewTicker(d)} 19 | } 20 | 21 | func (t *timeTicker) Tick() <-chan time.Time { 22 | return t.C 23 | } 24 | -------------------------------------------------------------------------------- /readers/README.md: -------------------------------------------------------------------------------- 1 | # Readers 2 | 3 | Readers provide implementations of various `message readers`. Message readers are services that consume normalized (in `SenML` format) SuperMQ messages from data storage and expose HTTP API for message consumption. 4 | 5 | For an in-depth explanation of the usage of `reader`, as well as thorough understanding of SuperMQ, please check out the [official documentation][doc]. 6 | 7 | [doc]: https://docs.supermq.abstractmachines.fr 8 | -------------------------------------------------------------------------------- /readers/api/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package api contains API-related concerns: endpoint definitions, middlewares 5 | // and all resource representations. 6 | package api 7 | -------------------------------------------------------------------------------- /readers/api/grpc/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package grpc contains implementation of Readers service gRPC API. 5 | package grpc 6 | -------------------------------------------------------------------------------- /readers/api/grpc/endpoint.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package grpc 5 | 6 | import ( 7 | "context" 8 | 9 | readers "github.com/absmach/supermq/readers" 10 | "github.com/go-kit/kit/endpoint" 11 | ) 12 | 13 | func readMessagesEndpoint(svc readers.MessageRepository) endpoint.Endpoint { 14 | return func(ctx context.Context, request interface{}) (interface{}, error) { 15 | req := request.(readMessagesReq) 16 | if err := req.validate(); err != nil { 17 | return readMessagesRes{}, err 18 | } 19 | 20 | page, err := svc.ReadAll(req.chanID, req.pageMeta) 21 | if err != nil { 22 | return readMessagesRes{}, err 23 | } 24 | 25 | return readMessagesRes{ 26 | PageMetadata: page.PageMetadata, 27 | Total: page.Total, 28 | Messages: page.Messages, 29 | }, nil 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /readers/api/grpc/request.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package grpc 5 | 6 | import ( 7 | "slices" 8 | "strings" 9 | "time" 10 | 11 | apiutil "github.com/absmach/supermq/api/http/util" 12 | "github.com/absmach/supermq/readers" 13 | ) 14 | 15 | const maxLimitSize = 1000 16 | 17 | var validAggregations = []string{"MAX", "MIN", "AVG", "SUM", "COUNT"} 18 | 19 | type readMessagesReq struct { 20 | chanID string 21 | domain string 22 | pageMeta readers.PageMetadata 23 | } 24 | 25 | func (req readMessagesReq) validate() error { 26 | if req.chanID == "" { 27 | return apiutil.ErrMissingID 28 | } 29 | if req.domain == "" { 30 | return apiutil.ErrMissingID 31 | } 32 | 33 | if req.pageMeta.Limit < 1 || req.pageMeta.Limit > maxLimitSize { 34 | return apiutil.ErrLimitSize 35 | } 36 | 37 | if req.pageMeta.Comparator != "" && 38 | req.pageMeta.Comparator != readers.EqualKey && 39 | req.pageMeta.Comparator != readers.LowerThanKey && 40 | req.pageMeta.Comparator != readers.LowerThanEqualKey && 41 | req.pageMeta.Comparator != readers.GreaterThanKey && 42 | req.pageMeta.Comparator != readers.GreaterThanEqualKey { 43 | return apiutil.ErrInvalidComparator 44 | } 45 | 46 | if req.pageMeta.Aggregation == "AGGREGATION_UNSPECIFIED" { 47 | req.pageMeta.Aggregation = "" 48 | } 49 | 50 | if agg := strings.ToUpper(req.pageMeta.Aggregation); agg != "" && agg != "AGGREGATION_UNSPECIFIED" { 51 | if req.pageMeta.From == 0 { 52 | return apiutil.ErrMissingFrom 53 | } 54 | 55 | if req.pageMeta.To == 0 { 56 | return apiutil.ErrMissingTo 57 | } 58 | 59 | if !slices.Contains(validAggregations, strings.ToUpper(req.pageMeta.Aggregation)) { 60 | return apiutil.ErrInvalidAggregation 61 | } 62 | 63 | if _, err := time.ParseDuration(req.pageMeta.Interval); err != nil { 64 | return apiutil.ErrInvalidInterval 65 | } 66 | } 67 | 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /readers/api/grpc/responses.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package grpc 5 | 6 | import ( 7 | "github.com/absmach/supermq/readers" 8 | ) 9 | 10 | type readMessagesRes struct { 11 | Total uint64 12 | Messages []readers.Message 13 | readers.PageMetadata 14 | } 15 | 16 | type Message interface{} 17 | -------------------------------------------------------------------------------- /readers/api/grpc/setup_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package grpc_test 5 | 6 | import ( 7 | "os" 8 | "testing" 9 | 10 | "github.com/absmach/supermq/readers/mocks" 11 | ) 12 | 13 | var svc *mocks.MessageRepository 14 | 15 | func TestMain(m *testing.M) { 16 | svc = new(mocks.MessageRepository) 17 | server := startGRPCServer(svc, port) 18 | 19 | code := m.Run() 20 | 21 | server.GracefulStop() 22 | 23 | os.Exit(code) 24 | } 25 | -------------------------------------------------------------------------------- /readers/api/http/endpoint.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package http 5 | 6 | import ( 7 | "context" 8 | 9 | grpcChannelsV1 "github.com/absmach/supermq/api/grpc/channels/v1" 10 | grpcClientsV1 "github.com/absmach/supermq/api/grpc/clients/v1" 11 | apiutil "github.com/absmach/supermq/api/http/util" 12 | smqauthn "github.com/absmach/supermq/pkg/authn" 13 | "github.com/absmach/supermq/pkg/errors" 14 | svcerr "github.com/absmach/supermq/pkg/errors/service" 15 | "github.com/absmach/supermq/readers" 16 | "github.com/go-kit/kit/endpoint" 17 | ) 18 | 19 | func listMessagesEndpoint(svc readers.MessageRepository, authn smqauthn.Authentication, clients grpcClientsV1.ClientsServiceClient, channels grpcChannelsV1.ChannelsServiceClient) endpoint.Endpoint { 20 | return func(ctx context.Context, request interface{}) (interface{}, error) { 21 | req := request.(listMessagesReq) 22 | if err := req.validate(); err != nil { 23 | return nil, errors.Wrap(apiutil.ErrValidation, err) 24 | } 25 | 26 | if err := authnAuthz(ctx, req, authn, clients, channels); err != nil { 27 | return nil, errors.Wrap(svcerr.ErrAuthorization, err) 28 | } 29 | 30 | page, err := svc.ReadAll(req.chanID, req.pageMeta) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | return pageRes{ 36 | PageMetadata: page.PageMetadata, 37 | Total: page.Total, 38 | Messages: page.Messages, 39 | }, nil 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /readers/api/http/requests.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package http 5 | 6 | import ( 7 | "slices" 8 | "strings" 9 | "time" 10 | 11 | apiutil "github.com/absmach/supermq/api/http/util" 12 | "github.com/absmach/supermq/readers" 13 | ) 14 | 15 | const maxLimitSize = 1000 16 | 17 | var validAggregations = []string{"MAX", "MIN", "AVG", "SUM", "COUNT"} 18 | 19 | type listMessagesReq struct { 20 | chanID string 21 | token string 22 | domain string 23 | key string 24 | pageMeta readers.PageMetadata 25 | } 26 | 27 | func (req listMessagesReq) validate() error { 28 | if req.token == "" && req.key == "" { 29 | return apiutil.ErrBearerToken 30 | } 31 | 32 | if req.chanID == "" { 33 | return apiutil.ErrMissingID 34 | } 35 | 36 | if req.pageMeta.Limit < 1 || req.pageMeta.Limit > maxLimitSize { 37 | return apiutil.ErrLimitSize 38 | } 39 | 40 | if req.pageMeta.Comparator != "" && 41 | req.pageMeta.Comparator != readers.EqualKey && 42 | req.pageMeta.Comparator != readers.LowerThanKey && 43 | req.pageMeta.Comparator != readers.LowerThanEqualKey && 44 | req.pageMeta.Comparator != readers.GreaterThanKey && 45 | req.pageMeta.Comparator != readers.GreaterThanEqualKey { 46 | return apiutil.ErrInvalidComparator 47 | } 48 | 49 | if req.pageMeta.Aggregation != "" { 50 | if req.pageMeta.From == 0 { 51 | return apiutil.ErrMissingFrom 52 | } 53 | 54 | if req.pageMeta.To == 0 { 55 | return apiutil.ErrMissingTo 56 | } 57 | 58 | if !slices.Contains(validAggregations, strings.ToUpper(req.pageMeta.Aggregation)) { 59 | return apiutil.ErrInvalidAggregation 60 | } 61 | 62 | if _, err := time.ParseDuration(req.pageMeta.Interval); err != nil { 63 | return apiutil.ErrInvalidInterval 64 | } 65 | } 66 | 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /readers/api/http/responses.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package http 5 | 6 | import ( 7 | "net/http" 8 | 9 | "github.com/absmach/supermq" 10 | "github.com/absmach/supermq/readers" 11 | ) 12 | 13 | var _ supermq.Response = (*pageRes)(nil) 14 | 15 | type pageRes struct { 16 | readers.PageMetadata 17 | Total uint64 `json:"total"` 18 | Messages []readers.Message `json:"messages"` 19 | } 20 | 21 | func (res pageRes) Headers() map[string]string { 22 | return map[string]string{} 23 | } 24 | 25 | func (res pageRes) Code() int { 26 | return http.StatusOK 27 | } 28 | 29 | func (res pageRes) Empty() bool { 30 | return false 31 | } 32 | -------------------------------------------------------------------------------- /readers/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package readers provides a set of readers for various formats. 5 | package readers 6 | -------------------------------------------------------------------------------- /readers/middleware/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package middleware provides middleware for Magistrala Readers service. 5 | package middleware 6 | -------------------------------------------------------------------------------- /readers/middleware/logging.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build !test 5 | 6 | package middleware 7 | 8 | import ( 9 | "log/slog" 10 | "time" 11 | 12 | "github.com/absmach/supermq/readers" 13 | ) 14 | 15 | var _ readers.MessageRepository = (*loggingMiddleware)(nil) 16 | 17 | type loggingMiddleware struct { 18 | logger *slog.Logger 19 | svc readers.MessageRepository 20 | } 21 | 22 | // LoggingMiddleware adds logging facilities to the core service. 23 | func LoggingMiddleware(svc readers.MessageRepository, logger *slog.Logger) readers.MessageRepository { 24 | return &loggingMiddleware{ 25 | logger: logger, 26 | svc: svc, 27 | } 28 | } 29 | 30 | func (lm *loggingMiddleware) ReadAll(chanID string, rpm readers.PageMetadata) (page readers.MessagesPage, err error) { 31 | defer func(begin time.Time) { 32 | args := []any{ 33 | slog.String("duration", time.Since(begin).String()), 34 | slog.String("channel_id", chanID), 35 | slog.Group("page", 36 | slog.Uint64("offset", rpm.Offset), 37 | slog.Uint64("limit", rpm.Limit), 38 | slog.Uint64("total", page.Total), 39 | ), 40 | } 41 | if rpm.Subtopic != "" { 42 | args = append(args, slog.String("subtopic", rpm.Subtopic)) 43 | } 44 | if rpm.Publisher != "" { 45 | args = append(args, slog.String("publisher", rpm.Publisher)) 46 | } 47 | if err != nil { 48 | args = append(args, slog.Any("error", err)) 49 | lm.logger.Warn("Read all failed", args...) 50 | return 51 | } 52 | lm.logger.Info("Read all completed successfully", args...) 53 | }(time.Now()) 54 | 55 | return lm.svc.ReadAll(chanID, rpm) 56 | } 57 | -------------------------------------------------------------------------------- /readers/middleware/metrics.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build !test 5 | 6 | package middleware 7 | 8 | import ( 9 | "time" 10 | 11 | "github.com/absmach/supermq/readers" 12 | "github.com/go-kit/kit/metrics" 13 | ) 14 | 15 | var _ readers.MessageRepository = (*metricsMiddleware)(nil) 16 | 17 | type metricsMiddleware struct { 18 | counter metrics.Counter 19 | latency metrics.Histogram 20 | svc readers.MessageRepository 21 | } 22 | 23 | // MetricsMiddleware instruments core service by tracking request count and latency. 24 | func MetricsMiddleware(svc readers.MessageRepository, counter metrics.Counter, latency metrics.Histogram) readers.MessageRepository { 25 | return &metricsMiddleware{ 26 | counter: counter, 27 | latency: latency, 28 | svc: svc, 29 | } 30 | } 31 | 32 | func (mm *metricsMiddleware) ReadAll(chanID string, rpm readers.PageMetadata) (readers.MessagesPage, error) { 33 | defer func(begin time.Time) { 34 | mm.counter.With("method", "read_all").Add(1) 35 | mm.latency.With("method", "read_all").Observe(time.Since(begin).Seconds()) 36 | }(time.Now()) 37 | 38 | return mm.svc.ReadAll(chanID, rpm) 39 | } 40 | -------------------------------------------------------------------------------- /readers/postgres/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package postgres contains repository implementations using Postgres as 5 | // the underlying database. 6 | package postgres 7 | -------------------------------------------------------------------------------- /readers/postgres/init.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package postgres 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/jmoiron/sqlx" 10 | migrate "github.com/rubenv/sql-migrate" 11 | ) 12 | 13 | // Table for SenML messages. 14 | const defTable = "messages" 15 | 16 | // Config defines the options that are used when connecting to a PostgreSQL instance. 17 | type Config struct { 18 | Host string 19 | Port string 20 | User string 21 | Pass string 22 | Name string 23 | SSLMode string 24 | SSLCert string 25 | SSLKey string 26 | SSLRootCert string 27 | } 28 | 29 | // Connect creates a connection to the PostgreSQL instance and applies any 30 | // unapplied database migrations. A non-nil error is returned to indicate 31 | // failure. 32 | func Connect(cfg Config) (*sqlx.DB, error) { 33 | url := fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=%s sslcert=%s sslkey=%s sslrootcert=%s", cfg.Host, cfg.Port, cfg.User, cfg.Name, cfg.Pass, cfg.SSLMode, cfg.SSLCert, cfg.SSLKey, cfg.SSLRootCert) 34 | 35 | db, err := sqlx.Open("pgx", url) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | if err := migrateDB(db); err != nil { 41 | return nil, err 42 | } 43 | 44 | return db, nil 45 | } 46 | 47 | func migrateDB(db *sqlx.DB) error { 48 | migrations := &migrate.MemoryMigrationSource{ 49 | Migrations: []*migrate.Migration{ 50 | { 51 | Id: "messages_1", 52 | Up: []string{ 53 | `CREATE TABLE IF NOT EXISTS messages ( 54 | id UUID, 55 | channel UUID, 56 | subtopic VARCHAR(254), 57 | publisher UUID, 58 | protocol TEXT, 59 | name TEXT, 60 | unit TEXT, 61 | value FLOAT, 62 | string_value TEXT, 63 | bool_value BOOL, 64 | data_value TEXT, 65 | sum FLOAT, 66 | time FlOAT, 67 | update_time FLOAT, 68 | PRIMARY KEY (id) 69 | )`, 70 | }, 71 | Down: []string{ 72 | "DROP TABLE messages", 73 | }, 74 | }, 75 | }, 76 | } 77 | 78 | _, err := migrate.Exec(db.DB, "postgres", migrations, migrate.Up) 79 | return err 80 | } 81 | -------------------------------------------------------------------------------- /readers/postgres/setup_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package postgres_test contains tests for PostgreSQL repository 5 | // implementations. 6 | package postgres_test 7 | 8 | import ( 9 | "fmt" 10 | "log" 11 | "os" 12 | "testing" 13 | 14 | "github.com/absmach/magistrala/readers/postgres" 15 | _ "github.com/jackc/pgx/v5/stdlib" // required for SQL access 16 | "github.com/jmoiron/sqlx" 17 | "github.com/ory/dockertest/v3" 18 | "github.com/ory/dockertest/v3/docker" 19 | ) 20 | 21 | var db *sqlx.DB 22 | 23 | func TestMain(m *testing.M) { 24 | pool, err := dockertest.NewPool("") 25 | if err != nil { 26 | log.Fatalf("Could not connect to docker: %s", err) 27 | } 28 | container, err := pool.RunWithOptions(&dockertest.RunOptions{ 29 | Repository: "postgres", 30 | Tag: "16.2-alpine", 31 | Env: []string{ 32 | "POSTGRES_USER=test", 33 | "POSTGRES_PASSWORD=test", 34 | "POSTGRES_DB=test", 35 | "listen_addresses = '*'", 36 | }, 37 | }, func(config *docker.HostConfig) { 38 | config.AutoRemove = true 39 | config.RestartPolicy = docker.RestartPolicy{Name: "no"} 40 | }) 41 | if err != nil { 42 | log.Fatalf("Could not start container: %s", err) 43 | } 44 | 45 | port := container.GetPort("5432/tcp") 46 | url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port) 47 | 48 | if err = pool.Retry(func() error { 49 | db, err = sqlx.Open("pgx", url) 50 | if err != nil { 51 | return err 52 | } 53 | return db.Ping() 54 | }); err != nil { 55 | log.Fatalf("Could not connect to docker: %s", err) 56 | } 57 | 58 | dbConfig := postgres.Config{ 59 | Host: "localhost", 60 | Port: port, 61 | User: "test", 62 | Pass: "test", 63 | Name: "test", 64 | SSLMode: "disable", 65 | SSLCert: "", 66 | SSLKey: "", 67 | SSLRootCert: "", 68 | } 69 | 70 | if db, err = postgres.Connect(dbConfig); err != nil { 71 | log.Fatalf("Could not setup test DB connection: %s", err) 72 | } 73 | 74 | code := m.Run() 75 | 76 | // Defers will not be run when using os.Exit 77 | db.Close() 78 | if err = pool.Purge(container); err != nil { 79 | log.Fatalf("Could not purge container: %s", err) 80 | } 81 | 82 | os.Exit(code) 83 | } 84 | -------------------------------------------------------------------------------- /readers/timescale/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package timescale contains repository implementations using Timescale as 5 | // the underlying database. 6 | package timescale 7 | -------------------------------------------------------------------------------- /scripts/csv/channels.csv: -------------------------------------------------------------------------------- 1 | channel_1 2 | channel_2 3 | channel_3 4 | -------------------------------------------------------------------------------- /scripts/csv/clients.csv: -------------------------------------------------------------------------------- 1 | client_1 2 | client_2 3 | client_3 4 | client_4 5 | client_5 6 | client_6 7 | client_7 8 | client_8 9 | client_9 10 | client_10 11 | -------------------------------------------------------------------------------- /scripts/gen-ts-data/.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | *.csv 5 | -------------------------------------------------------------------------------- /scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) Abstract Machines 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | ### 6 | # Runs all SuperMQ microservices (must be previously built and installed). 7 | # 8 | # Expects that PostgreSQL and needed messaging DB are alredy running. 9 | # Additionally, MQTT microservice demands that Redis is up and running. 10 | # 11 | ### 12 | 13 | BUILD_DIR=../build 14 | 15 | # Kill all supermq-* stuff 16 | function cleanup { 17 | pkill supermq 18 | pkill nats 19 | } 20 | 21 | ### 22 | # NATS 23 | ### 24 | nats-server & 25 | counter=1 26 | until fuser 4222/tcp 1>/dev/null 2>&1; 27 | do 28 | sleep 0.5 29 | ((counter++)) 30 | if [ ${counter} -gt 10 ] 31 | then 32 | echo "NATS failed to start in 5 sec, exiting" 33 | exit 1 34 | fi 35 | echo "Waiting for NATS server" 36 | done 37 | 38 | ### 39 | # Users 40 | ### 41 | SMQ_USERS_LOG_LEVEL=info SMQ_USERS_HTTP_PORT=9002 SMQ_USERS_GRPC_PORT=7001 SMQ_USERS_ADMIN_EMAIL=admin@supermq.com SMQ_USERS_ADMIN_PASSWORD=12345678 SMQ_USERS_ADMIN_USERNAME=admin SMQ_EMAIL_TEMPLATE=../docker/templates/users.tmpl $BUILD_DIR/supermq-users & 42 | 43 | ### 44 | # Clients 45 | ### 46 | SMQ_CLIENTS_LOG_LEVEL=info SMQ_CLIENTS_HTTP_PORT=9000 SMQ_CLIENTS_GRPC_PORT=7000 SMQ_CLIENTS_HTTP_PORT=9002 $BUILD_DIR/supermq-clients & 47 | 48 | ### 49 | # HTTP 50 | ### 51 | SMQ_HTTP_ADAPTER_LOG_LEVEL=info SMQ_HTTP_ADAPTER_PORT=8008 SMQ_CLIENTS_GRPC_URL=localhost:7000 $BUILD_DIR/supermq-http & 52 | 53 | ### 54 | # WS 55 | ### 56 | SMQ_WS_ADAPTER_LOG_LEVEL=info SMQ_WS_ADAPTER_HTTP_PORT=8190 SMQ_CLIENTS_GRPC_URL=localhost:7000 $BUILD_DIR/supermq-ws & 57 | 58 | ### 59 | # MQTT 60 | ### 61 | SMQ_MQTT_ADAPTER_LOG_LEVEL=info SMQ_CLIENTS_GRPC_URL=localhost:7000 $BUILD_DIR/supermq-mqtt & 62 | 63 | ### 64 | # CoAP 65 | ### 66 | SMQ_COAP_ADAPTER_LOG_LEVEL=info SMQ_COAP_ADAPTER_PORT=5683 SMQ_CLIENTS_GRPC_URL=localhost:7000 $BUILD_DIR/supermq-coap & 67 | 68 | trap cleanup EXIT 69 | 70 | while : ; do sleep 1 ; done 71 | -------------------------------------------------------------------------------- /scripts/supermq.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) Abstract Machines 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | ### 6 | # Fetches the latest version of the docker files from the SuperMQ repository. 7 | ### 8 | 9 | set -e 10 | set -o pipefail 11 | 12 | REPO_URL=https://github.com/absmach/supermq 13 | TEMP_DIR="supermq" 14 | DOCKER_DIR="docker" 15 | DOCKER_DST_DIR="../docker" 16 | DEST_DIR="../../docker/supermq-docker" 17 | 18 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 19 | cd "$SCRIPT_DIR" || exit 1 20 | 21 | if [ -n "$(git status --porcelain "$DOCKER_DST_DIR")" ]; then 22 | echo "There are uncommitted changes in '$DOCKER_DIR' dir. Please commit or stash them before running this script." 23 | exit 1 24 | fi 25 | 26 | cleanup() { 27 | rm -rf "$TEMP_DIR" 28 | } 29 | cleanup 30 | trap cleanup EXIT 31 | 32 | git clone --depth 1 --filter=blob:none --sparse "$REPO_URL" 33 | cd "$TEMP_DIR" 34 | git sparse-checkout set "$DOCKER_DIR" 35 | 36 | if [ -d "$DEST_DIR" ]; then 37 | rm -r "$DEST_DIR" 38 | fi 39 | mkdir -p "$DEST_DIR" 40 | mv -f "$DOCKER_DIR"/.??* "$DOCKER_DIR"/* "$DEST_DIR"/ 41 | cd .. 42 | rm -rf "$TEMP_DIR" 43 | -------------------------------------------------------------------------------- /tools/config/.codecov.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # CoAP is temporarily ignored since we don't have tests for it yet. 5 | coverage: 6 | ignore: 7 | - "tools/*" 8 | - "coap/*" 9 | - "**/mocks*" 10 | - "*/middleware/*" 11 | -------------------------------------------------------------------------------- /tools/config/.mockery.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | pkgname: mocks 4 | template: testify 5 | mockname: "{{.InterfaceName}}" 6 | filename: "{{snakecase .InterfaceName}}.go" 7 | dir: "{{.InterfaceDirRelative}}/mocks" 8 | force-file-write: true 9 | template-data: 10 | boilerplate-file: ./tools/config/boilerplate.txt 11 | with-expecter: true 12 | packages: 13 | github.com/absmach/magistrala/pkg/sdk: 14 | interfaces: 15 | SDK: 16 | config: 17 | mockname: "SDK" 18 | filename: "sdk.go" 19 | github.com/absmach/magistrala/re: 20 | interfaces: 21 | Repository: 22 | Service: 23 | Ticker: 24 | Emailer: 25 | github.com/absmach/magistrala/bootstrap: 26 | interfaces: 27 | ConfigRepository: 28 | ConfigReader: 29 | Service: 30 | github.com/absmach/magistrala/consumers/notifiers: 31 | interfaces: 32 | Service: 33 | SubscriptionsRepository: 34 | github.com/absmach/magistrala/provision: 35 | interfaces: 36 | Service: 37 | github.com/absmach/magistrala/alarms: 38 | interfaces: 39 | Service: 40 | Repository: 41 | github.com/absmach/magistrala/api/grpc/readers/v1: 42 | interfaces: 43 | ReadersServiceClient: 44 | config: 45 | dir: "./readers/mocks" 46 | mockname: "ReadersServiceClient" 47 | filename: "readers_client.go" 48 | -------------------------------------------------------------------------------- /tools/config/boilerplate.txt: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | 3 | // SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /tools/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package tools contains tools for SuperMQ. 5 | package tools 6 | -------------------------------------------------------------------------------- /tools/e2e/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | PROGRAM = e2e 5 | SOURCES = $(wildcard *.go) cmd/main.go 6 | 7 | all: $(PROGRAM) 8 | 9 | .PHONY: all clean 10 | 11 | $(PROGRAM): $(SOURCES) 12 | go build -ldflags "-s -w" -o $@ cmd/main.go 13 | 14 | clean: 15 | rm -rf $(PROGRAM) 16 | -------------------------------------------------------------------------------- /tools/e2e/cmd/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package main contains e2e tool for testing SuperMQ. 5 | package main 6 | 7 | import ( 8 | "log" 9 | 10 | "github.com/absmach/magistrala/tools/e2e" 11 | cc "github.com/ivanpirog/coloredcobra" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | const defNum = uint64(10) 16 | 17 | func main() { 18 | econf := e2e.Config{} 19 | 20 | rootCmd := &cobra.Command{ 21 | Use: "e2e", 22 | Short: "e2e is end-to-end testing tool for SuperMQ", 23 | Long: "Tool for testing end-to-end flow of supermq by doing a couple of operations namely:\n" + 24 | "1. Creating, viewing, updating and changing status of users, groups, clients and channels.\n" + 25 | "2. Connecting users and groups to each other and clients and channels to each other.\n" + 26 | "3. Sending messages from clients to channels on all 4 protocol adapters (HTTP, WS, CoAP and MQTT).\n" + 27 | "Complete documentation is available at https://docs.supermq.abstractmachines.fr", 28 | Example: "Here is a simple example of using e2e tool.\n" + 29 | "Use the following commands from the root supermq directory:\n\n" + 30 | "go run tools/e2e/cmd/main.go\n" + 31 | "go run tools/e2e/cmd/main.go --host 142.93.118.47\n" + 32 | "go run tools/e2e/cmd/main.go --host localhost --num 10 --num_of_messages 100 --prefix e2e", 33 | Run: func(cmd *cobra.Command, _ []string) { 34 | e2e.Test(cmd.Context(), econf) 35 | }, 36 | } 37 | 38 | cc.Init(&cc.Config{ 39 | RootCmd: rootCmd, 40 | Headings: cc.HiCyan + cc.Bold + cc.Underline, 41 | CmdShortDescr: cc.Magenta, 42 | Example: cc.Italic + cc.Magenta, 43 | ExecName: cc.Bold, 44 | Flags: cc.HiGreen + cc.Bold, 45 | FlagsDescr: cc.Green, 46 | FlagsDataType: cc.White + cc.Italic, 47 | }) 48 | 49 | // Root Flags 50 | rootCmd.PersistentFlags().StringVarP(&econf.Host, "host", "H", "localhost", "address for a running supermq instance") 51 | rootCmd.PersistentFlags().StringVarP(&econf.Prefix, "prefix", "p", "", "name prefix for users, groups, clients and channels") 52 | rootCmd.PersistentFlags().Uint64VarP(&econf.Num, "num", "n", defNum, "number of users, groups, channels and clients to create and connect") 53 | rootCmd.PersistentFlags().Uint64VarP(&econf.NumOfMsg, "num_of_messages", "N", defNum, "number of messages to send") 54 | 55 | if err := rootCmd.Execute(); err != nil { 56 | log.Fatal(err) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tools/e2e/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package e2e contains entry point for end-to-end tests. 5 | package e2e 6 | -------------------------------------------------------------------------------- /tools/mqtt-bench/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | PROGRAM = mqtt-bench 5 | SOURCES = $(wildcard *.go) cmd/main.go 6 | 7 | all: $(PROGRAM) 8 | 9 | .PHONY: all clean 10 | 11 | $(PROGRAM): $(SOURCES) 12 | go build -ldflags "-s -w" -o $@ cmd/main.go 13 | 14 | clean: 15 | rm -rf $(PROGRAM) 16 | -------------------------------------------------------------------------------- /tools/mqtt-bench/config.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package bench 5 | 6 | // Keep struct names exported, otherwise Viper unmarshalling won't work. 7 | type mqttBrokerConfig struct { 8 | URL string `toml:"url" mapstructure:"url"` 9 | } 10 | 11 | type mqttMessageConfig struct { 12 | Size int `toml:"size" mapstructure:"size"` 13 | Payload string `toml:"payload" mapstructure:"payload"` 14 | Format string `toml:"format" mapstructure:"format"` 15 | QoS int `toml:"qos" mapstructure:"qos"` 16 | Retain bool `toml:"retain" mapstructure:"retain"` 17 | } 18 | 19 | type mqttTLSConfig struct { 20 | MTLS bool `toml:"mtls" mapstructure:"mtls"` 21 | SkipTLSVer bool `toml:"skiptlsver" mapstructure:"skiptlsver"` 22 | CA string `toml:"ca" mapstructure:"ca"` 23 | } 24 | 25 | type mqttConfig struct { 26 | Broker mqttBrokerConfig `toml:"broker" mapstructure:"broker"` 27 | Message mqttMessageConfig `toml:"message" mapstructure:"message"` 28 | Timeout int `toml:"timeout" mapstructure:"timeout"` 29 | TLS mqttTLSConfig `toml:"tls" mapstructure:"tls"` 30 | } 31 | 32 | type testConfig struct { 33 | Count int `toml:"count" mapstructure:"count"` 34 | Pubs int `toml:"pubs" mapstructure:"pubs"` 35 | Subs int `toml:"subs" mapstructure:"subs"` 36 | } 37 | 38 | type logConfig struct { 39 | Quiet bool `toml:"quiet" mapstructure:"quiet"` 40 | } 41 | 42 | type smqFile struct { 43 | ConnFile string `toml:"connections_file" mapstructure:"connections_file"` 44 | } 45 | 46 | type client struct { 47 | ClientID string `toml:"client_id" mapstructure:"client_id"` 48 | ClientSecret string `toml:"client_secret" mapstructure:"client_secret"` 49 | MTLSCert string `toml:"mtls_cert" mapstructure:"mtls_cert"` 50 | MTLSKey string `toml:"mtls_key" mapstructure:"mtls_key"` 51 | } 52 | 53 | type channel struct { 54 | ChannelID string `toml:"channel_id" mapstructure:"channel_id"` 55 | } 56 | 57 | type superMQ struct { 58 | Clients []client `toml:"clients" mapstructure:"clients"` 59 | Channels []channel `toml:"channels" mapstructure:"channels"` 60 | } 61 | 62 | // Config struct holds benchmark configuration. 63 | type Config struct { 64 | MQTT mqttConfig `toml:"mqtt" mapstructure:"mqtt"` 65 | Test testConfig `toml:"test" mapstructure:"test"` 66 | Log logConfig `toml:"log" mapstructure:"log"` 67 | Smq smqFile `toml:"supermq" mapstructure:"supermq"` 68 | } 69 | -------------------------------------------------------------------------------- /tools/mqtt-bench/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package bench contains benchmarking tool for MQTT broker. 5 | package bench 6 | -------------------------------------------------------------------------------- /tools/mqtt-bench/scripts/mqtt-bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) Abstract Machines 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | i=0 6 | echo "BEGIN TEST " > result.$1.out 7 | for mtls in true 8 | do 9 | for ret in false true 10 | do 11 | for qos in 0 1 2 12 | do 13 | for pub in 1 10 100 14 | do 15 | for sub in 1 10 16 | do 17 | for message in 100 1000 18 | do 19 | if [[ $pub -eq 100 && $message -eq 1000 ]]; 20 | then 21 | continue 22 | fi 23 | 24 | for size in 100 500 25 | do 26 | let "i += 1" 27 | echo "=================================TEST $i=========================================" >> $1-$i.out 28 | echo "MTLS: $mtls RETAIN: $ret, QOS $qos" >> $1-$i.out 29 | echo "Pub:" $pub ", Sub:" $sub ", MsgSize:" $size ", MsgPerPub:" $message >> $1-$i.out 30 | echo "=================================================================================" >> $1-$i.out 31 | if [ "$mtls" = true ]; 32 | then 33 | echo "| " >> $1-$i.out 34 | echo "| ./mqtt-bench --channels $3 -s $size -n $message --subs $sub --pubs $pub -q $qos --retain=$ret -m=true -b tcps://$2:8883 --quiet=true --ca ../../../docker/ssl/certs/ca.crt -t=true" >> $1-$i.out 35 | echo "| " >> $1-$i.out 36 | ../cmd/mqtt-bench --channels $3 -s $size -n $message --subs $sub --pubs $pub -q $qos --retain=$ret -m=true -b tcps://$2:8883 --quiet=true --ca ../../../docker/ssl/certs/ca.crt -t=true >> $1-$i.out 37 | else 38 | echo "| " >> $1-$i.out 39 | echo "| ./mqtt-bench --channels $3 -s $size -n $message --subs $sub --pubs $pub -q $qos --retain=$ret -b tcp://$2:1883 --quiet=true" >> $1-$i.out 40 | echo "| " >> $1-$i.out 41 | ../cmd/mqtt-bench --channels $3 -s $size -n $message --subs $sub --pubs $pub -q $qos --retain=$ret -b tcp://$2:1883 --quiet=true >> $1-$i.out 42 | fi 43 | sleep 2 44 | done 45 | done 46 | done 47 | done 48 | done 49 | 50 | done 51 | done 52 | files=`ls test*.out | sort --version-sort ` 53 | for file in $files 54 | do 55 | cat $file >> result.$1.out 56 | done 57 | echo "END TEST " >> result.$1.out 58 | -------------------------------------------------------------------------------- /tools/mqtt-bench/templates/reference.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | [mqtt] 5 | timeout = 1000 6 | [mqtt.broker] 7 | url = "tcp://localhost:1883" 8 | 9 | [mqtt.message] 10 | size = 1000 11 | format = "text" 12 | qos = 2 13 | retain = true 14 | payload = "{\"bn\":\"some-base-name\",\"bt\":1.276020076001e+09, \"bu\":\"A\",\"bver\":5, \"n\":\"voltage\",\"u\":\"V\",\"v\":120.1}" 15 | 16 | [mqtt.tls] 17 | mtls = false 18 | skiptlsver = true 19 | ca = "ca.crt" 20 | 21 | [test] 22 | pubs = 2000 23 | count = 70 24 | 25 | [log] 26 | quiet = true 27 | 28 | [supermq] 29 | connections_file = "../provision/mgconn.toml" 30 | -------------------------------------------------------------------------------- /tools/provision/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) Abstract Machines 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | PROGRAM = provision 5 | SOURCES = $(wildcard *.go) cmd/main.go 6 | 7 | all: $(PROGRAM) 8 | 9 | .PHONY: all clean 10 | 11 | $(PROGRAM): $(SOURCES) 12 | go build -ldflags "-s -w" -o $@ cmd/main.go 13 | 14 | clean: 15 | rm -rf $(PROGRAM) 16 | -------------------------------------------------------------------------------- /tools/provision/cmd/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package main contains entry point for provisioning tool. 5 | package main 6 | 7 | import ( 8 | "log" 9 | 10 | "github.com/absmach/magistrala/tools/provision" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func main() { 15 | pconf := provision.Config{} 16 | 17 | rootCmd := &cobra.Command{ 18 | Use: "provision", 19 | Short: "provision is provisioning tool for SuperMQ", 20 | Long: `Tool for provisioning series of SuperMQ channels and clients and connecting them together. 21 | Complete documentation is available at https://docs.supermq.abstractmachines.fr`, 22 | Run: func(cmd *cobra.Command, _ []string) { 23 | if err := provision.Provision(cmd.Context(), pconf); err != nil { 24 | log.Fatal(err) 25 | } 26 | }, 27 | } 28 | 29 | // Root Flags 30 | rootCmd.PersistentFlags().StringVarP(&pconf.Host, "host", "", "https://localhost", "address for supermq instance") 31 | rootCmd.PersistentFlags().StringVarP(&pconf.Prefix, "prefix", "", "", "name prefix for clients and channels") 32 | rootCmd.PersistentFlags().StringVarP(&pconf.Username, "username", "u", "", "supermq user") 33 | rootCmd.PersistentFlags().StringVarP(&pconf.Password, "password", "p", "", "supermq users password") 34 | rootCmd.PersistentFlags().IntVarP(&pconf.Num, "num", "", 10, "number of channels and clients to create and connect") 35 | rootCmd.PersistentFlags().BoolVarP(&pconf.SSL, "ssl", "", false, "create certificates for mTLS access") 36 | rootCmd.PersistentFlags().StringVarP(&pconf.CAKey, "cakey", "", "ca.key", "ca.key for creating and signing clients certificate") 37 | rootCmd.PersistentFlags().StringVarP(&pconf.CA, "ca", "", "ca.crt", "CA for creating and signing clients certificate") 38 | 39 | if err := rootCmd.Execute(); err != nil { 40 | log.Fatal(err) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tools/provision/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Abstract Machines 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package provision is a simple utility to create 5 | // a list of channels and clients connected to these channels 6 | // with possibility to create certificates for mTLS use case. 7 | package provision 8 | --------------------------------------------------------------------------------