Files

101 lines
3.4 KiB
Rego

package compose
import future.keywords.if
import future.keywords.contains
import future.keywords.in
# Every service must have a restart policy
deny_no_restart contains msg if {
some name, svc in input.services
not svc.restart
msg := sprintf("service %s: missing restart policy", [name])
}
# zigbee2mqtt must use the Pine64 BLZ fork, not upstream koenkk
deny_wrong_image contains msg if {
svc := input.services.zigbee2mqtt
not startswith(svc.image, "ghcr.io/pine64/zigbee2mqtt")
msg := sprintf("zigbee2mqtt: image must be ghcr.io/pine64/zigbee2mqtt (got %s)", [svc.image])
}
# zigbee2mqtt must have the correct entrypoint
deny_missing_entrypoint contains msg if {
svc := input.services.zigbee2mqtt
svc.entrypoint != ["/app/data/scripts/entrypoint.sh"]
msg := "zigbee2mqtt: entrypoint must be [\"/app/data/scripts/entrypoint.sh\"]"
}
# zigbee2mqtt must have the correct command (tini wrapper)
deny_wrong_command contains msg if {
svc := input.services.zigbee2mqtt
svc.command != ["/sbin/tini", "--", "node", "index.js"]
msg := sprintf("zigbee2mqtt: command must be /sbin/tini -- node index.js (got %s)", [svc.command])
}
# Device sources should use stable by-path or by-id symlinks, not raw /dev/ttyUSB*
deny_raw_tty contains msg if {
some name, svc in input.services
some dev in svc.devices
startswith(dev.source, "/dev/ttyUSB")
msg := sprintf("service %s: device source %s uses raw tty path (use /dev/serial/by-*)", [name, dev.source])
}
# Only audio-bridge should run privileged
deny_unexpected_privileged contains msg if {
some name, svc in input.services
svc.privileged
name != "audio-bridge"
msg := sprintf("service %s: privileged: true (only audio-bridge should be privileged)", [name])
}
# zigbee2mqtt must depend on mosquitto
deny_missing_dep contains msg if {
svc := input.services.zigbee2mqtt
not svc.depends_on
msg := "zigbee2mqtt: missing depends_on (should depend on mosquitto)"
}
deny_missing_dep contains msg if {
svc := input.services.zigbee2mqtt
svc.depends_on
not "mosquitto" in object.keys(svc.depends_on)
msg := "zigbee2mqtt: must depend on mosquitto"
}
# nodered must depend on mosquitto with condition: service_healthy
deny_nodered_dep_healthy contains msg if {
svc := input.services.nodered
dep := svc.depends_on.mosquitto
dep.condition != "service_healthy"
msg := "nodered: mosquitto dependency must have condition: service_healthy"
}
# zigbee2mqtt should not use host networking
deny_host_network contains msg if {
svc := input.services.zigbee2mqtt
svc.network_mode == "host"
msg := "zigbee2mqtt: should not use host network mode"
}
# zigbee2mqtt must mount zigbee2mqtt/ as /app/data
has_z2m_vol if {
some vol in input.services.zigbee2mqtt.volumes
startswith(vol, "./zigbee2mqtt:/app/data")
}
deny_missing_volume contains msg if {
svc := input.services.zigbee2mqtt
not has_z2m_vol
msg := "zigbee2mqtt: must have volume mapping ./zigbee2mqtt:/app/data"
}
# Cross-reference: zigbee2mqtt device target must match its config port.
# Load configuration.yaml via --data flag:
# conftest test infra/compose.yaml --data infra/zigbee2mqtt/configuration.yaml
deny_port_mismatch contains msg if {
svc := input.services.zigbee2mqtt
some dev in svc.devices
cfg := data.configuration.serial.port
dev.target != cfg
msg := sprintf("zigbee2mqtt: device target %s does not match config port %s", [dev.target, cfg])
}