moat-link-notify 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,14 @@
1
+ The code in this repository, and all MoaT submodules it refers to,
2
+ is part of the MoaT project.
3
+
4
+ Unless a submodule's LICENSE.txt states otherwise, all included files are
5
+ licensed under the LGPL V3, as published by the FSF at
6
+ https://www.gnu.org/licenses/lgpl-3.0.html .
7
+
8
+ In addition to the LGPL's terms, the author(s) respectfully ask all users of
9
+ this code to contribute any bug fixes or enhancements. Also, please link back to
10
+ https://M-o-a-T.org.
11
+
12
+ Thank you.
13
+
14
+ Copyright © 2021 ff.: the MoaT contributor(s), as per the git changelog(s).
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/make -f
2
+
3
+ PACKAGE = moat-link-notify
4
+ MAKEINCL ?= $(shell python3 -mmoat src path)/make/py
5
+
6
+ install: installsystemd
7
+
8
+ installsystemd:
9
+ mkdir -p $(PREFIX)/lib/systemd/system
10
+ cp systemd/*.service $(PREFIX)/lib/systemd/system/
11
+
12
+ ifneq ($(wildcard $(MAKEINCL)),)
13
+ include $(MAKEINCL)
14
+ # availabe via http://github.com/smurfix/sourcemgr
15
+
16
+ else
17
+ %:
18
+ @echo "Please fix 'python3 -mmoat src path'."
19
+ @exit 1
20
+ endif
@@ -0,0 +1,50 @@
1
+ Metadata-Version: 2.4
2
+ Name: moat-link-notify
3
+ Version: 0.0.1
4
+ Summary: Notification gateway for MoaT-Link
5
+ Author-email: Matthias Urlichs <matthias@urlichs.de>
6
+ Project-URL: homepage, https://m-o-a-t.org
7
+ Project-URL: repository, https://github.com/M-o-a-T/moat
8
+ Keywords: MoaT
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Framework :: AsyncIO
12
+ Classifier: Framework :: Trio
13
+ Classifier: Framework :: AnyIO
14
+ Classifier: Development Status :: 4 - Beta
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE.txt
18
+ Requires-Dist: anyio~=4.2
19
+ Requires-Dist: asyncclick
20
+ Requires-Dist: attrs
21
+ Requires-Dist: httpx
22
+ Requires-Dist: moat-util~=0.62.2
23
+ Requires-Dist: moat-link~=0.2.46
24
+ Dynamic: license-file
25
+
26
+ # moat-link-notify
27
+
28
+ Notification gateway for MoaT-Link.
29
+
30
+ This package provides notification services for MoaT-Link, allowing you to send
31
+ notifications to external services like ntfy.sh.
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ pip install moat-link-notify
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ The notification gateway can be configured through MoaT-Link's configuration system.
42
+
43
+ ## Dependencies
44
+
45
+ - moat-link: Core MoaT-Link functionality
46
+ - httpx: HTTP client for notification delivery
47
+
48
+ ## License
49
+
50
+ This project is licensed under the same terms as MoaT.
@@ -0,0 +1,25 @@
1
+ # moat-link-notify
2
+
3
+ Notification gateway for MoaT-Link.
4
+
5
+ This package provides notification services for MoaT-Link, allowing you to send
6
+ notifications to external services like ntfy.sh.
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ pip install moat-link-notify
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ The notification gateway can be configured through MoaT-Link's configuration system.
17
+
18
+ ## Dependencies
19
+
20
+ - moat-link: Core MoaT-Link functionality
21
+ - httpx: HTTP client for notification delivery
22
+
23
+ ## License
24
+
25
+ This project is licensed under the same terms as MoaT.
@@ -0,0 +1,6 @@
1
+ *.debhelper
2
+ *.substvars
3
+ files
4
+ .debhelper/
5
+ moat-link-notify/
6
+ debhelper-build-stamp
@@ -0,0 +1,11 @@
1
+ moat-link-notify (0.0.1-2) unstable; urgency=medium
2
+
3
+ * New release for 25.8.6
4
+
5
+ -- Matthias Urlichs <matthias@urlichs.de> Mon, 05 Jan 2026 16:50:07 +0100
6
+
7
+ moat-link-notify (0.0.0-0) unstable; urgency=medium
8
+
9
+ * Initial release as separate package
10
+
11
+ -- Matthias Urlichs <matthias@urlichs.de> Sun, 05 Jan 2025 13:00:00 +0100
@@ -0,0 +1,26 @@
1
+ Source: moat-link-notify
2
+ Maintainer: Matthias Urlichs <matthias@urlichs.de>
3
+ Section: python
4
+ Priority: optional
5
+ Build-Depends: dh-python, python3-all, debhelper (>= 13),
6
+ python3-setuptools,
7
+ python3-wheel,
8
+ moat-src,
9
+ Standards-Version: 3.9.6
10
+ Homepage: https://github.com/smurfix/moat
11
+ X-DH-Compat: 13
12
+
13
+ Package: moat-link-notify
14
+ Architecture: all
15
+ Depends: ${misc:Depends}, ${python3:Depends},
16
+ moat-util,
17
+ moat-link,
18
+ python3-httpx,
19
+ systemd (>= 241),
20
+ Recommends:
21
+ python3-trio (>= 0.22),
22
+ Description: Notification gateway for MoaT-Link
23
+ This package provides notification services for MoaT-Link, allowing you to
24
+ send notifications to external services like ntfy.sh.
25
+ .
26
+ Part of the MoaT ecosystem for distributed home automation.
@@ -0,0 +1,23 @@
1
+ [Unit]
2
+ Description=MoaT-Link Notification Forwarder
3
+
4
+ ConditionFileNotEmpty=/etc/moat/moat.cfg
5
+
6
+ [Install]
7
+ WantedBy=multi-user.target
8
+
9
+ [Service]
10
+ Type=notify
11
+ ExecStart=/usr/bin/moat link notify run
12
+
13
+ RuntimeDirectory=moat/link
14
+
15
+ User=MoaT-Link
16
+ Group=MoaT
17
+
18
+ EnvironmentFile=/usr/lib/moat/link/env
19
+ EnvironmentFile=-/etc/moat/link.env
20
+
21
+ TimeoutSec=300
22
+ WatchdogSec=10
23
+ Restart=always
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/make -f
2
+
3
+ export PYBUILD_NAME=moat-link-notify
4
+ %:
5
+ dh $@ --with python3 --buildsystem=pybuild
6
+
7
+ execute_after_dh_install:
8
+ $(MAKE) installsystemd PREFIX=debian/moat-link-notify
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ build-backend = "setuptools.build_meta"
3
+ requires = [ "setuptools", "wheel",]
4
+
5
+ [project]
6
+ classifiers = [
7
+ "Intended Audience :: Developers",
8
+ "Programming Language :: Python :: 3",
9
+ "Framework :: AsyncIO",
10
+ "Framework :: Trio",
11
+ "Framework :: AnyIO",
12
+ "Development Status :: 4 - Beta",
13
+ ]
14
+ dependencies = [
15
+ "anyio ~= 4.2",
16
+ "asyncclick",
17
+ "attrs",
18
+ "httpx",
19
+ "moat-util ~= 0.62.2",
20
+ "moat-link ~= 0.2.46",
21
+ ]
22
+ version = "0.0.1"
23
+ keywords = [ "MoaT",]
24
+ requires-python = ">=3.8"
25
+ name = "moat-link-notify"
26
+ description = "Notification gateway for MoaT-Link"
27
+ readme = "README.md"
28
+
29
+ [[project.authors]]
30
+ name = "Matthias Urlichs"
31
+ email = "matthias@urlichs.de"
32
+
33
+ [project.urls]
34
+ homepage = "https://m-o-a-t.org"
35
+ repository = "https://github.com/M-o-a-T/moat"
36
+
37
+ [tool.setuptools]
38
+ [tool.setuptools.packages.find]
39
+ where = ["src"]
40
+
41
+ [tool.setuptools.package-data]
42
+ "*" = ["*.yaml"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,227 @@
1
+ """
2
+ Notification package
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import anyio
8
+ import logging
9
+ import time
10
+ from abc import ABCMeta, abstractmethod
11
+ from contextlib import AsyncExitStack, asynccontextmanager
12
+
13
+ from moat.util import CtxObj, NotGiven, P, Path, as_service, attrdict
14
+ from moat.lib.priomap import PrioMap
15
+
16
+ from typing import TYPE_CHECKING
17
+
18
+ if TYPE_CHECKING:
19
+ from moat.link.client import Link
20
+
21
+ from collections.abc import AsyncIterator
22
+ from typing import NoReturn, Self
23
+
24
+ __all__ = ["Notifier", "Notify", "get_backend"]
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ class Notify:
30
+ """
31
+ Notification runner.
32
+ """
33
+
34
+ link: Link
35
+
36
+ def __init__(self, cfg):
37
+ self.cfg = cfg
38
+ self.dropped: PrioMap[Notifier] = PrioMap()
39
+
40
+ async def run(self, link: Link, evt: anyio.Event | None = None):
41
+ """
42
+ Task that reads notifications from MoaT-Link and posts them.
43
+ """
44
+ self.link = link
45
+ async with AsyncExitStack() as ex:
46
+ backend = self.cfg.backend
47
+ if isinstance(backend, str):
48
+ backend = (backend,)
49
+
50
+ self._backends = {}
51
+ for name in backend:
52
+ cfg = self.cfg.get(name, attrdict())
53
+ try:
54
+ notifier = await ex.enter_async_context(get_backend(name, link, cfg))
55
+ except Exception as exc:
56
+ logger.warning("Backend %r", name, exc_info=exc)
57
+ else:
58
+ self._backends[name] = notifier
59
+
60
+ if not self._backends:
61
+ raise RuntimeError("No backend worked.")
62
+
63
+ try:
64
+ await self._run(evt)
65
+ finally:
66
+ with anyio.move_on_after(2, shield=True):
67
+ await self.send(
68
+ topic="error.notify",
69
+ title="Backend stopped",
70
+ msg="The backend terminated.",
71
+ prio="fatal",
72
+ )
73
+
74
+ async def send(self, **kw) -> None:
75
+ """Send this notification to that topic."""
76
+ # reactivate dropped items
77
+ tm = time.monotonic()
78
+ retry: set[Notifier] = set()
79
+ error_seen = False
80
+
81
+ # Collect failed backends for retrying
82
+ while self.dropped:
83
+ b_e, t = self.dropped.peek()
84
+ if t > tm:
85
+ break
86
+ del self.dropped[b_e]
87
+ retry.add(b_e)
88
+
89
+ for name, b_e in list(self._backends.items()):
90
+ if b_e in self.dropped:
91
+ continue
92
+ try:
93
+ await b_e.send(**kw)
94
+ except Exception as exc:
95
+ # Log failed backends only once
96
+ if b_e in self.dropped:
97
+ continue
98
+ error_seen = True
99
+
100
+ self.dropped[b_e] = tm + self.cfg.timeout.retry
101
+ await self.link.e_exc(
102
+ P("run.notify.backend") / name,
103
+ exc,
104
+ msg=f"The notification backend {name} failed.",
105
+ val=kw,
106
+ level=3,
107
+ )
108
+ else:
109
+ if b_e in retry:
110
+ # No lock because this can only happen in one branch
111
+ await self.link.e_ok(P("run.notify.backend") / name)
112
+
113
+ if len(self.dropped) >= len(self._backends):
114
+ # This might conceivably happen in more than one branch
115
+ # if more than one backend fails at a time, but we don't care
116
+ if error_seen:
117
+ await self.link.e_info(
118
+ P("run.notify.backend"), "All notification backends failed.", level=4
119
+ )
120
+ elif any(x not in self.dropped for x in retry):
121
+ await self.link.e_ok(P("run.notify.backend"))
122
+
123
+ async def _run(self, evt) -> NoReturn:
124
+ """
125
+ A bridge that monitors the MoaT-Link notify subtree.
126
+ """
127
+ async with as_service(attrdict(debug=False)) as srv:
128
+ try:
129
+ await srv.tg.start(self._keepalive)
130
+
131
+ async with self.link.d_watch(
132
+ self.cfg.path, subtree=True, meta=True, state=None
133
+ ) as mon:
134
+ srv.set()
135
+ if evt is not None:
136
+ evt.set()
137
+ async for path, msg, meta in mon:
138
+ t = time.time()
139
+ if meta.timestamp < t - self.cfg.max_age:
140
+ continue
141
+ if isinstance(msg, NotGiven):
142
+ # Treat as empty message, for now
143
+ msg = {"msg": "Deleted"} # noqa:PLW2901
144
+ if isinstance(msg, dict):
145
+ if "title" not in msg:
146
+ msg["title"] = str(path)
147
+ await self.send(topic=path, **msg)
148
+ else:
149
+ await self.send(topic=path, title="?", msg=str(msg))
150
+
151
+ # not reached, loop doesn't terminate
152
+
153
+ except Exception as exc:
154
+ await self.send(
155
+ topic=P("error.notify"), msg=repr(exc), prio="error", title="Gateway failure"
156
+ )
157
+ raise
158
+
159
+ async def _keepalive(self, *, task_status=anyio.TASK_STATUS_IGNORED) -> NoReturn:
160
+ "Monitor the keepalive topic"
161
+ bad = True
162
+ link = self.link
163
+ keep = self.cfg.keepalive
164
+ ok_keep = keep.ok
165
+
166
+ timeout = keep.get("timeout", link.cfg.timeout.ping.timeout)
167
+
168
+ # The main host watcher publishes on the empty path
169
+ async with link.d_watch(P("run.host"), state=None) as mon:
170
+ task_status.started()
171
+ mon = aiter(mon) # noqa:PLW2901
172
+ while True:
173
+ if bad:
174
+ msg = await anext(mon)
175
+ else:
176
+ try:
177
+ with anyio.fail_after(timeout):
178
+ msg = await anext(mon)
179
+ except TimeoutError:
180
+ msg = None
181
+
182
+ if isinstance(msg, dict) and "id" in msg:
183
+ async with link.d_watch(P("run.ping.id") / msg["id"]) as mon2:
184
+ mon2 = aiter(mon2) # noqa:PLW2901
185
+ while True:
186
+ try:
187
+ with anyio.fail_after(timeout):
188
+ msg = await anext(mon2)
189
+ if not msg.get("up", False):
190
+ break
191
+ if bad and "msg" in ok_keep:
192
+ await self.send(topic="error.notify", **ok_keep)
193
+ bad = False
194
+ except TimeoutError:
195
+ break
196
+
197
+ await self.send(topic="error.notify", **keep)
198
+ bad = True
199
+
200
+
201
+ def get_backend(name: str, link: Link, cfg: dict) -> Notify:
202
+ """
203
+ Fetch the backend named in the config and initialize it.
204
+ """
205
+ from importlib import import_module # noqa: PLC0415
206
+
207
+ if "." not in name:
208
+ name = "moat.link.notify." + name
209
+ return import_module(name).Notifier(link, cfg)
210
+
211
+
212
+ class Notifier(CtxObj, metaclass=ABCMeta):
213
+ "Base class for notification backends"
214
+
215
+ def __init__(self, link: Link, cfg: attrdict):
216
+ self.cfg = cfg
217
+ self.link = link
218
+
219
+ @asynccontextmanager
220
+ async def _ctx(self) -> AsyncIterator[Self]:
221
+ yield self
222
+
223
+ @abstractmethod
224
+ async def send(
225
+ self, topic: str | Path, title: str | None = None, msg: str | None = None, **kw
226
+ ):
227
+ """Send this notification to that topic."""
@@ -0,0 +1,32 @@
1
+ # Notifications are messages below :R.notify.TOPIC topic.
2
+ keepalive:
3
+ # timeout: 30 # default: ping *1.5
4
+ #
5
+ # topic: my_secret_topic
6
+ title: Server Link down?
7
+ msg: |
8
+ The MoaT system at 'some_host' is silent. (MQTT-Keepalive)
9
+ prio: fatal
10
+ ok:
11
+ title: Server Link OK
12
+ msg: |
13
+ The MoaT system at 'some_host' is back up. (MQTT-Keepalive)
14
+ prio: info
15
+
16
+ timeout:
17
+ retry: 300
18
+
19
+ path: !P notify
20
+ # ignore messages older than … seconds
21
+ max_age: 30
22
+
23
+ # Backends
24
+ backend:
25
+ - ntfy
26
+ ntfy:
27
+ # See https://docs.nfty.sh/
28
+
29
+ url: https://ntfy.sh.example/
30
+ # token: tk_whatever
31
+ # messages are posted to notify.TOPIC.WHATEVER if "topic" is not set
32
+ # topic: my_secret_topic
@@ -0,0 +1,37 @@
1
+ # command line interface
2
+ from __future__ import annotations
3
+
4
+ import asyncclick as click
5
+
6
+ from moat.link.announce import announcing
7
+ from moat.link.client import Link
8
+
9
+
10
+ @click.group(short_help="Manage notifications.") # pylint: disable=undefined-variable
11
+ @click.pass_context
12
+ async def cli(ctx):
13
+ """
14
+ Handle notifications.
15
+ """
16
+ obj = ctx.obj
17
+ cfg = obj.cfg["link"]
18
+ obj.conn = await ctx.with_async_resource(Link(cfg))
19
+
20
+
21
+ @cli.command()
22
+ @click.option("-b", "--backend", type=str, multiple=True, help="Restrict to this backend")
23
+ @click.pass_obj
24
+ async def run(obj, backend):
25
+ """
26
+ Forward notification messages.
27
+
28
+ This command monitors the 'notify' subpath and forwards messages to
29
+ your ntfy.sh instance.
30
+ """
31
+ from moat.link.notify import Notify # noqa: PLC0415
32
+
33
+ cfg = obj.cfg.link.notify
34
+ if backend:
35
+ cfg.backends = backend
36
+ async with announcing(obj.conn) as ann:
37
+ await Notify(cfg).run(obj.conn, evt=ann)
@@ -0,0 +1,86 @@
1
+ """
2
+ This module forwards notifications to NTFY.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from contextlib import asynccontextmanager
8
+
9
+ import httpx
10
+
11
+ from moat.util import P, Path
12
+
13
+ from . import Notifier as BaseNotifier
14
+
15
+ from typing import TYPE_CHECKING
16
+
17
+ if TYPE_CHECKING:
18
+ from collections.abc import Sequence
19
+
20
+ __all__ = ["Notifier"]
21
+
22
+ prio_map = {
23
+ "debug": 1,
24
+ "info": 2,
25
+ "warning": 3,
26
+ "error": 4,
27
+ "fatal": 5,
28
+ }
29
+
30
+
31
+ class Notifier(BaseNotifier): # noqa:D101
32
+ @asynccontextmanager
33
+ async def _ctx(self):
34
+ async with (
35
+ super()._ctx(),
36
+ httpx.AsyncClient() as self.http,
37
+ ):
38
+ yield self
39
+
40
+ async def send(
41
+ self,
42
+ topic: str | Path,
43
+ title: str | None = None,
44
+ msg: str | None = None,
45
+ prio: str | None = None,
46
+ tags: Sequence[str] = (),
47
+ **kw, # noqa: ARG002
48
+ ):
49
+ "Forward a message to NTFY"
50
+
51
+ if "topic" in self.cfg:
52
+ top = self.cfg.topic
53
+ else:
54
+ if isinstance(topic, str):
55
+ topic = P(topic)
56
+ try:
57
+ top = topic[0]
58
+ except IndexError:
59
+ top = "TOP"
60
+ else:
61
+ topic = Path.build(topic[1:])
62
+
63
+ url = self.cfg.url + "/" + top
64
+ tags = list(tags)
65
+ tags.append(str(topic))
66
+
67
+ hdr = {}
68
+
69
+ if title:
70
+ hdr["title"] = title
71
+ if prio is not None:
72
+ try:
73
+ prio = prio_map[prio]
74
+ hdr["prio"] = str(prio)
75
+ if prio > 3:
76
+ tags.append("warning")
77
+ except KeyError:
78
+ hdr["prio"] = "high"
79
+ msg += f" (prio:{prio})"
80
+ hdr["tags"] = ",".join(str(x) for x in tags)
81
+
82
+ if "token" in self.cfg:
83
+ hdr["authorization"] = f"Bearer {self.cfg['token']}"
84
+
85
+ resp = await self.http.post(url, data=msg, headers=hdr)
86
+ resp.raise_for_status()
@@ -0,0 +1,50 @@
1
+ Metadata-Version: 2.4
2
+ Name: moat-link-notify
3
+ Version: 0.0.1
4
+ Summary: Notification gateway for MoaT-Link
5
+ Author-email: Matthias Urlichs <matthias@urlichs.de>
6
+ Project-URL: homepage, https://m-o-a-t.org
7
+ Project-URL: repository, https://github.com/M-o-a-T/moat
8
+ Keywords: MoaT
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Framework :: AsyncIO
12
+ Classifier: Framework :: Trio
13
+ Classifier: Framework :: AnyIO
14
+ Classifier: Development Status :: 4 - Beta
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE.txt
18
+ Requires-Dist: anyio~=4.2
19
+ Requires-Dist: asyncclick
20
+ Requires-Dist: attrs
21
+ Requires-Dist: httpx
22
+ Requires-Dist: moat-util~=0.62.2
23
+ Requires-Dist: moat-link~=0.2.46
24
+ Dynamic: license-file
25
+
26
+ # moat-link-notify
27
+
28
+ Notification gateway for MoaT-Link.
29
+
30
+ This package provides notification services for MoaT-Link, allowing you to send
31
+ notifications to external services like ntfy.sh.
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ pip install moat-link-notify
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ The notification gateway can be configured through MoaT-Link's configuration system.
42
+
43
+ ## Dependencies
44
+
45
+ - moat-link: Core MoaT-Link functionality
46
+ - httpx: HTTP client for notification delivery
47
+
48
+ ## License
49
+
50
+ This project is licensed under the same terms as MoaT.
@@ -0,0 +1,19 @@
1
+ LICENSE.txt
2
+ Makefile
3
+ README.md
4
+ pyproject.toml
5
+ debian/.gitignore
6
+ debian/changelog
7
+ debian/control
8
+ debian/moat-link-notify.service
9
+ debian/rules
10
+ src/moat/link/notify/__init__.py
11
+ src/moat/link/notify/_cfg.yaml
12
+ src/moat/link/notify/_main.py
13
+ src/moat/link/notify/ntfy.py
14
+ src/moat_link_notify.egg-info/PKG-INFO
15
+ src/moat_link_notify.egg-info/SOURCES.txt
16
+ src/moat_link_notify.egg-info/dependency_links.txt
17
+ src/moat_link_notify.egg-info/requires.txt
18
+ src/moat_link_notify.egg-info/top_level.txt
19
+ systemd/moat-link-notify.service
@@ -0,0 +1,6 @@
1
+ anyio~=4.2
2
+ asyncclick
3
+ attrs
4
+ httpx
5
+ moat-util~=0.62.2
6
+ moat-link~=0.2.46
@@ -0,0 +1,23 @@
1
+ [Unit]
2
+ Description=MoaT-Link Notification Forwarder
3
+
4
+ ConditionFileNotEmpty=/etc/moat/moat.cfg
5
+
6
+ [Install]
7
+ WantedBy=multi-user.target
8
+
9
+ [Service]
10
+ Type=notify
11
+ ExecStart=/usr/bin/moat link notify run
12
+
13
+ RuntimeDirectory=moat/link
14
+
15
+ User=MoaT-Link
16
+ Group=MoaT
17
+
18
+ EnvironmentFile=/usr/lib/moat/link/env
19
+ EnvironmentFile=-/etc/moat/link.env
20
+
21
+ TimeoutSec=300
22
+ WatchdogSec=10
23
+ Restart=always