install as service

This commit is contained in:
2026-08-30 12:41:40 +02:00
parent 3224f27f39
commit 2cab3f6c45
8 changed files with 111 additions and 3 deletions
+43
View File
@@ -55,6 +55,13 @@ Run the service; host, port, and URL prefix all come from `config.yaml`:
.venv/bin/python wsgi.py .venv/bin/python wsgi.py
``` ```
For production, use the Waitress entry point. It reads host, port, thread count,
and all application settings from `config.yaml`:
```sh
.venv/bin/python serve.py
```
With the example settings, open <http://localhost:30225/w/>. Keep one application worker because the polling With the example settings, open <http://localhost:30225/w/>. Keep one application worker because the polling
scheduler runs inside the service process. Alternatively set scheduler runs inside the service process. Alternatively set
`collector.enabled: false` in web workers and invoke this from a system timer: `collector.enabled: false` in web workers and invoke this from a system timer:
@@ -78,6 +85,42 @@ The service account must have write permission on the log directory. Console
logging remains enabled by default so `python wsgi.py` displays its listening logging remains enabled by default so `python wsgi.py` displays its listening
address; set `logging.console: false` to use only the logfile. address; set `logging.console: false` to use only the logfile.
## systemd installation
The included `deploy/netatmo.service` runs Waitress from `/srv/service_netatmo`
under a dedicated, non-login `netatmo` account. As root, create the account and
writable data directory:
```sh
useradd --system --home-dir /srv/service_netatmo --shell /usr/sbin/nologin netatmo
install -d -o netatmo -g netatmo -m 0750 /var/lib/rrd
chown netatmo:netatmo /srv/service_netatmo/config.yaml
chmod 0600 /srv/service_netatmo/config.yaml
```
Create the virtual environment and install dependencies as that account:
```sh
sudo -u netatmo python3 -m venv /srv/service_netatmo/.venv
sudo -u netatmo /srv/service_netatmo/.venv/bin/pip install -r /srv/service_netatmo/requirements.txt
```
Install and start the unit:
```sh
install -o root -g root -m 0644 /srv/service_netatmo/deploy/netatmo.service /etc/systemd/system/netatmo.service
systemctl daemon-reload
systemctl enable --now netatmo.service
systemctl status netatmo.service
```
Follow service output and the rotating application log with:
```sh
journalctl -u netatmo.service -f
tail -f /var/lib/rrd/netatmo_service.log
```
## HTTP API ## HTTP API
```text ```text
+3 -2
View File
@@ -2,6 +2,7 @@
server: server:
host: 127.0.0.1 host: 127.0.0.1
port: 30225 port: 30225
threads: 4
url_prefix: /w url_prefix: /w
storage: storage:
@@ -23,8 +24,8 @@ graphs:
height: 240 height: 240
netatmo: netatmo:
client_id: 6a93db3d7cdb0e40ea0c2655 client_id: REPLACE_ME
client_secret: c6uRbAzZ9e8STPtXas6PYKaZZiD68Y6QHFaKq2GEIa8 client_secret: REPLACE_ME
redirect_uri: http://www.example.com/w redirect_uri: http://www.example.com/w
token_file: /var/lib/rrd/netatmo_tokens.json token_file: /var/lib/rrd/netatmo_tokens.json
device_id: "" device_id: ""
+25
View File
@@ -0,0 +1,25 @@
[Unit]
Description=Netatmo RRD Flask service (Waitress)
Documentation=file:///srv/service_netatmo/README.md
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=netatmo
Group=netatmo
WorkingDirectory=/srv/service_netatmo
ExecStart=/srv/service_netatmo/.venv/bin/python /srv/service_netatmo/serve.py
Restart=on-failure
RestartSec=10
TimeoutStopSec=30
UMask=0077
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
ReadWritePaths=/var/lib/rrd
[Install]
WantedBy=multi-user.target
+2 -1
View File
@@ -20,7 +20,7 @@ def default_config(base: Path | None = None) -> dict[str, Any]:
base = (base or Path.cwd()).resolve() base = (base or Path.cwd()).resolve()
rrd = base / "rrd" rrd = base / "rrd"
return { return {
"HOST": "0.0.0.0", "PORT": 5000, "URL_PREFIX": "", "RRD_FOLDER": rrd, "HOST": "0.0.0.0", "PORT": 5000, "THREADS": 4, "URL_PREFIX": "", "RRD_FOLDER": rrd,
"LOG_FILE": rrd / "netatmo_service.log", "LOG_LEVEL": "INFO", "LOG_FILE": rrd / "netatmo_service.log", "LOG_LEVEL": "INFO",
"LOG_MAX_BYTES": 5242880, "LOG_BACKUP_COUNT": 5, "LOG_CONSOLE": True, "LOG_MAX_BYTES": 5242880, "LOG_BACKUP_COUNT": 5, "LOG_CONSOLE": True,
"NETATMO_TOKEN_FILE": rrd / "netatmo_tokens.json", "NETATMO_TOKEN_FILE": rrd / "netatmo_tokens.json",
@@ -63,6 +63,7 @@ def load_config(filename: str | Path | None = None) -> dict[str, Any]:
result.update({ result.update({
"HOST": str(server.get("host", result["HOST"])), "HOST": str(server.get("host", result["HOST"])),
"PORT": int(server.get("port", result["PORT"])), "PORT": int(server.get("port", result["PORT"])),
"THREADS": int(server.get("threads", result["THREADS"])),
"URL_PREFIX": _prefix(server.get("url_prefix", result["URL_PREFIX"])), "URL_PREFIX": _prefix(server.get("url_prefix", result["URL_PREFIX"])),
"GRAPH_WIDTH": int(graphs.get("width", result["GRAPH_WIDTH"])), "GRAPH_WIDTH": int(graphs.get("width", result["GRAPH_WIDTH"])),
"GRAPH_HEIGHT": int(graphs.get("height", result["GRAPH_HEIGHT"])), "GRAPH_HEIGHT": int(graphs.get("height", result["GRAPH_HEIGHT"])),
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
# run as root
useradd --system --home-dir /srv/service_netatmo --shell /usr/sbin/nologin netatmo
install -d -o netatmo -g netatmo -m 0750 /var/lib/rrd
chown netatmo:netatmo /srv/service_netatmo/config.yaml
chmod 0600 /srv/service_netatmo/config.yaml
# sudo -u netatmo python3 -m venv /srv/service_netatmo/.venv
# sudo -u netatmo /srv/service_netatmo/.venv/bin/pip install \
-r /srv/service_netatmo/requirements.txt
install -o root -g root -m 0644 \
/srv/service_netatmo/deploy/netatmo.service \
/etc/systemd/system/netatmo.service
systemctl daemon-reload
systemctl enable --now netatmo.service
systemctl status netatmo.service
+1
View File
@@ -1,4 +1,5 @@
Flask>=3.1,<4 Flask>=3.1,<4
PyYAML>=6.0,<7 PyYAML>=6.0,<7
waitress>=3.0,<4
rrdtool-bindings # if python 3.14 rrdtool-bindings # if python 3.14
rrdtool # if python 3.12 rrdtool # if python 3.12
+14
View File
@@ -0,0 +1,14 @@
"""Production entry point using Waitress and config.yaml."""
from waitress import serve
from wsgi import app
if __name__ == "__main__":
serve(
app,
host=app.config["HOST"],
port=app.config["PORT"],
threads=app.config["THREADS"],
)
+2
View File
@@ -8,6 +8,7 @@ def test_yaml_config_is_loaded_and_relative_paths_are_resolved(tmp_path):
server: server:
host: 127.0.0.1 host: 127.0.0.1
port: 12345 port: 12345
threads: 6
url_prefix: weather/ url_prefix: weather/
storage: storage:
rrd_folder: data rrd_folder: data
@@ -27,6 +28,7 @@ modules:
config = load_config(config_file) config = load_config(config_file)
assert config["HOST"] == "127.0.0.1" assert config["HOST"] == "127.0.0.1"
assert config["PORT"] == 12345 assert config["PORT"] == 12345
assert config["THREADS"] == 6
assert config["URL_PREFIX"] == "/weather" assert config["URL_PREFIX"] == "/weather"
assert config["RRD_FOLDER"] == tmp_path / "data" assert config["RRD_FOLDER"] == tmp_path / "data"
assert config["LOG_FILE"] == tmp_path / "logs/service.log" assert config["LOG_FILE"] == tmp_path / "logs/service.log"