Skip to content

Service Deployment

This guide covers deploying aranet-service as a persistent background service on various platforms.

PlatformService ManagerConfig Location
macOSlaunchd~/Library/LaunchAgents/
Linuxsystemd/etc/systemd/system/
Dockerdocker-composeContainer orchestration
WindowsNSSMWindows Service
  1. Install aranet-service

    Terminal window
    cargo install aranet-service --features full
  2. Install as user service

    Terminal window
    aranet-service service install --user
  3. Start the service

    Terminal window
    aranet-service service start --user
  4. Check status

    Terminal window
    aranet-service service status --user

Create ~/Library/LaunchAgents/dev.rye.aranet.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>dev.rye.aranet</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/aranet-service</string>
<string>run</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/usr/local/var/log/aranet.log</string>
<key>StandardErrorPath</key>
<string>/usr/local/var/log/aranet.err</string>
<key>WorkingDirectory</key>
<string>/usr/local/var/aranet</string>
</dict>
</plist>

Load the service:

Terminal window
launchctl load ~/Library/LaunchAgents/dev.rye.aranet.plist
  1. Build and install

    Terminal window
    cargo build --release -p aranet-service --features full
    sudo cp target/release/aranet-service /usr/local/bin/
  2. Create service user

    Terminal window
    sudo useradd -r -s /bin/false aranet
    sudo mkdir -p /var/lib/aranet
    sudo chown aranet:aranet /var/lib/aranet
  3. Install the service file

    Terminal window
    aranet-service service install
    # Or manually: sudo cp distribution/systemd/aranet.service /etc/systemd/system/
  4. Enable and start

    Terminal window
    sudo systemctl daemon-reload
    sudo systemctl enable --now dev.rye.aranet

Create /etc/systemd/system/aranet.service:

[Unit]
Description=Aranet Sensor Collector Service
Documentation=https://github.com/cameronrye/aranet
After=network-online.target bluetooth.target
Wants=network-online.target
[Service]
Type=simple
User=aranet
Group=aranet
ExecStart=/usr/local/bin/aranet-service run
Restart=on-failure
RestartSec=5
StartLimitIntervalSec=60
StartLimitBurst=3
# Paths
WorkingDirectory=/var/lib/aranet
ReadWritePaths=/var/lib/aranet
# Security hardening
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
NoNewPrivileges=true
# Bluetooth access
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW
[Install]
WantedBy=multi-user.target
Terminal window
# Create user service directory
mkdir -p ~/.config/systemd/user
# Copy service file
cp distribution/systemd/aranet.service ~/.config/systemd/user/
# Edit to remove User/Group and adjust paths
# Then enable
systemctl --user daemon-reload
systemctl --user enable --now dev.rye.aranet

A multi-stage Dockerfile and docker-compose.yml are provided at the repository root. BLE access requires privileged mode and D-Bus passthrough.

8080/dashboard
# Standalone service with BLE
docker compose up -d

Use the docker/ directory for a Prometheus + Grafana stack:

Terminal window
docker compose -f docker/docker-compose.yml up -d
Terminal window
docker build -t aranet-service .
docker run -d --name aranet-service \
--privileged --network host \
-v /var/run/dbus:/var/run/dbus \
-v aranet-data:/data \
-v ./server.toml:/app/server.toml:ro \
-e RUST_LOG=aranet_service=info \
aranet-service
docker-compose.prod.yml
services:
aranet-service:
build: .
restart: always
ports:
- "8080:8080"
volumes:
- aranet-data:/data
- ./server.toml:/app/server.toml:ro
- /var/run/dbus:/var/run/dbus
privileged: true
network_mode: host
environment:
- RUST_LOG=aranet_service=info
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 5s
volumes:
aranet-data:

Create ~/.config/aranet/server.toml:

[server]
bind = "127.0.0.1:8080"
[storage]
path = "~/.local/share/aranet/data.db"
[prometheus]
enabled = true
[[devices]]
address = "Aranet4 XXXXX"
alias = "office"
poll_interval = 60

For production deployments, enable API authentication and rate limiting:

[security]
api_key_enabled = true
api_key = "your-secure-random-key-at-least-32-chars"
rate_limit_enabled = true
rate_limit_requests = 100
rate_limit_window_secs = 60

See API Security for detailed configuration.

Configure HTTP POST alerts when sensor thresholds are exceeded:

[webhooks]
enabled = true
co2_threshold = 1000 # ppm
radon_threshold = 300 # Bq/m³
battery_threshold = 10 # percent
cooldown_secs = 300 # seconds between repeat alerts per device
[[webhooks.endpoints]]
url = "https://hooks.slack.com/services/T00/B00/xxx"
events = ["co2_high", "radon_high", "battery_low"]
[[webhooks.endpoints]]
url = "https://ntfy.sh/my-aranet-alerts"
events = ["co2_high"]
headers = { "Authorization" = "Bearer tk_xxx" }

Each webhook POST sends a JSON payload:

{
"event": "co2_high",
"device_id": "Aranet4 AAAAA",
"alias": "office",
"value": 1200.0,
"threshold": 1000.0,
"unit": "ppm",
"reading": { ... },
"timestamp": "2026-03-28T12:00:00Z"
}

Valid event types: co2_high, radon_high, battery_low.

Export sensor readings to InfluxDB v2 in real time:

[influxdb]
enabled = true
url = "http://localhost:8086"
token = "your-influxdb-token"
org = "my-org"
bucket = "aranet"
measurement = "aranet"
precision = "s" # s, ms, us, or ns

Data is written using the InfluxDB v2 line protocol: aranet,device=office,address=Aranet4+AAAAA co2=450i,temperature=22.50,humidity=45i,pressure=1013.50,battery=85i.

When running, aranet-service automatically advertises itself on the local network via mDNS:

  • Service type: _aranet._tcp.local.
  • HTTP service: _http._tcp.local.
  • Properties: version, path (/api), dashboard (/dashboard)

Clients can discover the service without manual IP configuration:

Terminal window
# macOS
dns-sd -B _aranet._tcp local.
# Linux (avahi)
avahi-browse -r _aranet._tcp

A pre-built Grafana dashboard is included at grafana/aranet-dashboard.json. To import:

  1. Open Grafana and navigate to Dashboards > Import
  2. Click Upload JSON file and select grafana/aranet-dashboard.json
  3. Select your Prometheus data source and click Import

The dashboard includes panels for:

  • CO₂ gauges with threshold coloring
  • Environment time series (temperature, humidity, pressure)
  • Radon and radiation displays
  • Battery levels per device
  • Collector statistics (poll success/failure, duration)
Terminal window
# Log level
export RUST_LOG=aranet_service=info
# Config file location
export ARANET_CONFIG=/path/to/server.toml

Add to prometheus.yml:

scrape_configs:
- job_name: 'aranet'
static_configs:
- targets: ['localhost:8080']
metrics_path: /metrics
scrape_interval: 60s
MetricTypeDescription
aranet_co2_ppmgaugeCO₂ concentration
aranet_temperature_celsiusgaugeTemperature
aranet_humidity_percentgaugeRelative humidity
aranet_pressure_hpagaugeAtmospheric pressure
aranet_battery_percentgaugeBattery level
aranet_reading_age_secondsgaugeTime since last reading
aranet_collector_runninggaugeCollector status (1/0)
aranet_collector_uptime_secondsgaugeCollector uptime
aranet_device_poll_success_totalcounterSuccessful polls
aranet_device_poll_failure_totalcounterFailed polls
aranet_device_poll_duration_msgaugePer-device poll duration

Configure multiple devices in server.toml:

[[devices]]
address = "Aranet4 AAAAA"
alias = "office"
poll_interval = 60
[[devices]]
address = "Aranet4 BBBBB"
alias = "bedroom"
poll_interval = 120
[[devices]]
address = "Aranet4 CCCCC"
alias = "kitchen"
poll_interval = 60
[[devices]]
address = "AranetRn+ DDDDD"
alias = "basement"
poll_interval = 300 # Radon updates slowly

For sensors spread across multiple locations:

  1. Option A: Central collector - One aranet-service with MQTT forwarding
  2. Option B: Distributed collectors - Multiple aranet-service instances pushing to central Prometheus
┌─────────────────┐ ┌─────────────────┐
│ Location A │ │ Location B │
│ aranet-service │────▶│ aranet-service │
│ :8080 │ │ :8080 │
└────────┬────────┘ └────────┬────────┘
│ │
└──────────┬────────────┘
┌────────────────┐
│ Prometheus │
│ (Central) │
└───────┬────────┘
┌────────────────┐
│ Grafana │
└────────────────┘
Terminal window
# systemd
journalctl -u aranet -f
# launchd
tail -f /usr/local/var/log/aranet.log
# Docker
docker logs -f aranet-service
Terminal window
# Basic health check
curl http://localhost:8080/api/health
# {"status":"ok","version":"0.2.0","timestamp":"2026-03-28T10:30:00Z"}
# Detailed health check with diagnostics
curl http://localhost:8080/api/health/detailed
# Returns database, collector, and platform diagnostics
Terminal window
curl http://localhost:8080/metrics
  1. Check logs for errors
  2. Verify configuration file syntax: toml-cli check server.toml
  3. Ensure Bluetooth permissions (Linux: CAP_NET_ADMIN)
  • Linux: Add user to bluetooth group: sudo usermod -aG bluetooth $USER
  • macOS: Grant Bluetooth permission in System Preferences
  • Docker: Run with --privileged or mount /var/run/dbus
  1. Check device is powered on and in range
  2. Increase poll_interval (device may be busy)
  3. Verify no other app is connected to the device