131 lines
4.3 KiB
Python
131 lines
4.3 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import ipaddress
|
|
import os
|
|
import secrets
|
|
import subprocess
|
|
from pathlib import Path
|
|
from typing import Final
|
|
|
|
ROOT: Final = Path(__file__).resolve().parent
|
|
ENV_PATH: Final = ROOT / ".env"
|
|
RUNTIME: Final = ROOT / "runtime" / "mosquitto"
|
|
IMAGE: Final = "eclipse-mosquitto:2.1.2-alpine"
|
|
PLACEHOLDER: Final = "replace-with-"
|
|
|
|
|
|
def _initialize_environment(bind_address: str, *, overwrite: bool = False) -> None:
|
|
if ENV_PATH.exists() and not overwrite:
|
|
raise RuntimeError(".env already exists; refusing to overwrite local credentials")
|
|
normalized_bind_address = str(ipaddress.ip_address(bind_address))
|
|
values = {
|
|
"MISSIONCORE_MQTT_BIND_ADDRESS": normalized_bind_address,
|
|
"MISSIONCORE_MQTT_PORT": "1883",
|
|
"MISSIONCORE_TELEMETRY_QUERY_PORT": "18030",
|
|
"MISSIONCORE_DB_NAME": "missioncore_telemetry",
|
|
"MISSIONCORE_DB_USER": "missioncore_ingest",
|
|
"MISSIONCORE_DB_PASSWORD": secrets.token_urlsafe(36),
|
|
"MISSIONCORE_MQTT_INGEST_USER": "missioncore-ingest",
|
|
"MISSIONCORE_MQTT_INGEST_PASSWORD": secrets.token_urlsafe(36),
|
|
"MISSIONCORE_MQTT_WORKER_006_USER": "worker-006",
|
|
"MISSIONCORE_MQTT_WORKER_006_PASSWORD": secrets.token_urlsafe(36),
|
|
}
|
|
ENV_PATH.write_text(
|
|
"".join(f"{name}={value}\n" for name, value in values.items()),
|
|
encoding="utf-8",
|
|
)
|
|
os.chmod(ENV_PATH, 0o600)
|
|
|
|
|
|
def _environment() -> dict[str, str]:
|
|
if not ENV_PATH.is_file():
|
|
raise RuntimeError("copy .env.example to .env and set unique secrets first")
|
|
values: dict[str, str] = {}
|
|
for raw_line in ENV_PATH.read_text(encoding="utf-8").splitlines():
|
|
line = raw_line.strip()
|
|
if not line or line.startswith("#") or "=" not in line:
|
|
continue
|
|
name, value = line.split("=", 1)
|
|
values[name.strip()] = value.strip()
|
|
return values
|
|
|
|
|
|
def _required(values: dict[str, str], name: str) -> str:
|
|
value = values.get(name, "")
|
|
if not value or value.startswith(PLACEHOLDER):
|
|
raise RuntimeError(f"{name} must contain a non-placeholder value")
|
|
return value
|
|
|
|
|
|
def _password_entry(path: Path, username: str, password: str, *, create: bool) -> None:
|
|
command = [
|
|
"docker",
|
|
"run",
|
|
"--rm",
|
|
"-i",
|
|
"-v",
|
|
f"{RUNTIME}:/out",
|
|
IMAGE,
|
|
"mosquitto_passwd",
|
|
]
|
|
if create:
|
|
command.append("-c")
|
|
command.extend([f"/out/{path.name}", username])
|
|
subprocess.run(
|
|
command,
|
|
check=True,
|
|
input=f"{password}\n{password}\n",
|
|
text=True,
|
|
)
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
"--initialize",
|
|
action="store_true",
|
|
help="create a private .env with generated local credentials",
|
|
)
|
|
parser.add_argument(
|
|
"--mqtt-bind-address",
|
|
default="127.0.0.1",
|
|
help="host IP exposed to telemetry agents",
|
|
)
|
|
arguments = parser.parse_args()
|
|
if arguments.initialize:
|
|
_initialize_environment(arguments.mqtt_bind_address)
|
|
values = _environment()
|
|
ingest_user = _required(values, "MISSIONCORE_MQTT_INGEST_USER")
|
|
ingest_password = _required(values, "MISSIONCORE_MQTT_INGEST_PASSWORD")
|
|
worker_user = _required(values, "MISSIONCORE_MQTT_WORKER_006_USER")
|
|
worker_password = _required(values, "MISSIONCORE_MQTT_WORKER_006_PASSWORD")
|
|
_required(values, "MISSIONCORE_DB_PASSWORD")
|
|
|
|
RUNTIME.mkdir(parents=True, exist_ok=True)
|
|
password_path = RUNTIME / "passwords"
|
|
_password_entry(password_path, ingest_user, ingest_password, create=True)
|
|
_password_entry(password_path, worker_user, worker_password, create=False)
|
|
acl_path = RUNTIME / "acl"
|
|
acl_path.write_text(
|
|
"\n".join(
|
|
[
|
|
f"user {ingest_user}",
|
|
"topic read mission-core/v1/contours/+/agents/+/+",
|
|
"topic read $SYS/broker/uptime",
|
|
"",
|
|
"# Agent username must equal its stable agent id.",
|
|
"pattern write mission-core/v1/contours/+/agents/%u/+",
|
|
"",
|
|
]
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
os.chmod(password_path, 0o600)
|
|
os.chmod(acl_path, 0o600)
|
|
print("Mosquitto password and ACL files prepared.", flush=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|