From 6dbf6d047b273c5fdadc95b9fb0564805247d337 Mon Sep 17 00:00:00 2001 From: SimmerV Date: Thu, 5 Mar 2026 22:31:26 -0800 Subject: [PATCH 1/4] migration from .json to .TOML --- .gitignore | 1 + POSTGRES.md | 80 ++++---- README.md | 2 +- config.py | 78 +++++--- config.toml.sample | 212 ++++++++++++++++++++++ docker-compose-dev.yml | 2 +- docker-compose.yml | 2 +- memory_data_store.py | 3 +- scripts/migrate_json_to_postgres.py | 6 +- tests/test_assets_present.py | 2 +- tests/test_config_sample_is_valid_json.py | 13 +- 11 files changed, 321 insertions(+), 80 deletions(-) create mode 100644 config.toml.sample diff --git a/.gitignore b/.gitignore index 6789ad5d..5e249ab0 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ output/* mosquitto/data/* postgres/data/* config.json +config.toml spa .claude/ diff --git a/POSTGRES.md b/POSTGRES.md index a720e02c..ae7c83b8 100644 --- a/POSTGRES.md +++ b/POSTGRES.md @@ -13,25 +13,22 @@ MeshInfo now supports PostgreSQL as an alternative storage backend alongside JSO ## Configuration -Add the following to your `config.json`: - -```json -{ - "storage": { - "read_from": "json", - "write_to": ["json", "postgres"], - "postgres": { - "enabled": false, - "host": "postgres", - "port": 5432, - "database": "meshinfo", - "username": "postgres", - "password": "password", - "min_pool_size": 5, - "max_pool_size": 20 - } - } -} +Add the following to your `config.toml`: + +```toml +[storage] +read_from = "json" +write_to = ["json", "postgres"] + +[storage.postgres] +enabled = false +host = "postgres" +port = 5432 +database = "meshinfo" +username = "postgres" +password = "password" +min_pool_size = 5 +max_pool_size = 20 ``` ### Configuration Options @@ -68,15 +65,10 @@ To migrate existing JSON data to PostgreSQL: ### Step 1: Enable PostgreSQL -Update `config.json`: -```json -{ - "storage": { - "postgres": { - "enabled": true - } - } -} +Update `config.toml`: +```toml +[storage.postgres] +enabled = true ``` ### Step 2: Start PostgreSQL @@ -102,17 +94,14 @@ The script will: ### Step 4: Enable Dual-Write -Update `config.json` to write to both backends: -```json -{ - "storage": { - "read_from": "json", - "write_to": ["json", "postgres"], - "postgres": { - "enabled": true - } - } -} +Update `config.toml` to write to both backends: +```toml +[storage] +read_from = "json" +write_to = ["json", "postgres"] + +[storage.postgres] +enabled = true ``` ### Step 5: Verify Data Consistency @@ -124,13 +113,10 @@ Update `config.json` to write to both backends: ### Step 6: Switch to PostgreSQL Reads Once confident in data consistency, switch to direct PostgreSQL queries: -```json -{ - "storage": { - "read_from": "postgres", - "write_to": ["json", "postgres"] - } -} +```toml +[storage] +read_from = "postgres" +write_to = ["json", "postgres"] ``` **Important**: When `read_from: "postgres"`, the API queries PostgreSQL directly without loading data into memory. This provides: @@ -222,7 +208,7 @@ Monitor these logs to ensure healthy operation. If PostgreSQL connection fails: 1. Check that PostgreSQL container is running -2. Verify connection settings in config.json +2. Verify connection settings in config.toml 3. Check network connectivity 4. Review PostgreSQL logs diff --git a/README.md b/README.md index b4af92d8..9188fb86 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ cd meshinfo ##### Edit Configuration -1. Copy and then edit the `config.json.sample` to `config.json`. +1. Copy and then edit the `config.toml.sample` to `config.toml` (or `config.json.sample` to `config.json` for legacy JSON format). 2. Copy `Caddyfile.sample` to `Caddyfile` then edit the `Caddyfile` and be sure it is setup for your hostname (FQDN if requiring Let's Encrypt cert to be generated) and your email address for the TLS line. - Caddy will request a cert of the FQDN, be sure to specify any subdomain. For example: `https://meshinfo.domain.com`. diff --git a/config.py b/config.py index dd21e2aa..8f11d92d 100644 --- a/config.py +++ b/config.py @@ -12,6 +12,8 @@ import datetime import json import logging +import os +import tomllib import uuid from copy import deepcopy from typing import Any @@ -414,19 +416,19 @@ def check(result: str | None) -> None: "\n" "To fix this:\n" " 1. Add a PostgreSQL service to your docker-compose.yml (see docker-compose.yml.sample)\n" - " 2. Enable PostgreSQL in config.json:\n" - ' "storage": {\n' - ' "read_from": "postgres",\n' - ' "write_to": ["postgres"],\n' - ' "postgres": {\n' - ' "enabled": true,\n' - ' "host": "postgres",\n' - ' "port": 5432,\n' - ' "database": "meshinfo",\n' - ' "username": "postgres",\n' - ' "password": "your_password"\n' - " }\n" - " }\n" + " 2. Enable PostgreSQL in config.toml:\n" + " [storage]\n" + ' read_from = "postgres"\n' + ' write_to = ["postgres"]\n' + "\n" + " [storage.postgres]\n" + " enabled = true\n" + ' host = "postgres"\n' + " port = 5432\n" + ' database = "meshinfo"\n' + ' username = "postgres"\n' + ' password = "your_password"\n' + "\n" " 3. If migrating from JSON, run: docker exec -it meshinfo-meshinfo-1 python3 scripts/migrate_json_to_postgres.py\n" " 4. Restart MeshInfo\n" ) @@ -493,11 +495,23 @@ class Config: @classmethod def load(cls) -> dict: """ - Load config.json, merge with defaults, validate, and return - the final config dict. + Load config.toml (or config.json as fallback), merge with defaults, + validate, and return the final config dict. """ - # Load user config - user_config = cls._load_from_file("config.json") + # Load user config: prefer TOML, fall back to JSON + if os.path.exists("config.toml"): + user_config = cls._load_toml("config.toml") + elif os.path.exists("config.json"): + logger.warning( + "Loading config.json (JSON format is deprecated). " + "Please migrate to config.toml. See config.toml.sample for the format." + ) + user_config = cls._load_json("config.json") + else: + raise ConfigValidationError( + "No config file found. " + "Copy config.toml.sample to config.toml and edit it for your deployment." + ) # Merge: defaults first, user overrides on top config = _deep_merge(DEFAULT_CONFIG, user_config) @@ -515,7 +529,7 @@ def load(cls) -> dict: # Version info (optional, best-effort) try: - version_info = cls._load_from_file("version-info.json") + version_info = cls._load_json("version-info.json") if version_info is not None: config["server"]["version_info"] = version_info except (ConfigValidationError, FileNotFoundError, json.JSONDecodeError): @@ -532,15 +546,31 @@ def load(cls) -> dict: return config @classmethod - def _load_from_file(cls, path: str) -> dict: - """Load and parse a JSON file, with a clear error on failure.""" + def _load_toml(cls, path: str) -> dict: + """Load and parse a TOML file, with a clear error on failure.""" + try: + with open(path, "rb") as f: + return tomllib.load(f) + except FileNotFoundError: + raise ConfigValidationError( + f"Config file '{path}' not found. " + f"Copy config.toml.sample to config.toml and edit it for your deployment." + ) + except tomllib.TOMLDecodeError as e: + raise ConfigValidationError( + f"Config file '{path}' contains invalid TOML: {e}" + ) + + @classmethod + def _load_json(cls, path: str) -> dict: + """Load and parse a JSON file (legacy format).""" try: with open(path, "r", encoding="utf-8") as f: return json.load(f) except FileNotFoundError: raise ConfigValidationError( f"Config file '{path}' not found. " - f"Copy config.json.sample to config.json and edit it for your deployment." + f"Copy config.toml.sample to config.toml and edit it for your deployment." ) except json.JSONDecodeError as e: raise ConfigValidationError( @@ -549,8 +579,10 @@ def _load_from_file(cls, path: str) -> dict: @classmethod def load_from_file(cls, path: str) -> dict: - """Public alias for backward compatibility.""" - return cls._load_from_file(path) + """Load a config file by extension (TOML or JSON).""" + if ".toml" in path: + return cls._load_toml(path) + return cls._load_json(path) @classmethod def cleanse(cls, config: dict) -> dict: diff --git a/config.toml.sample b/config.toml.sample new file mode 100644 index 00000000..4a109e5e --- /dev/null +++ b/config.toml.sample @@ -0,0 +1,212 @@ +# MeshInfo Configuration +# Copy this file to config.toml and edit it for your deployment. + +debug = false + +[mesh] +name = "Central Valley Mesh" +shortname = "CVM" +description = "Serving Meshtastic to the California Central Valley and surrounding areas." +url = "https://CVME.sh" +contact = "https://CVME.sh" +country = "US" +region = "California" +metro = "Fresno, Modesto, Bakersfield" +latitude = 36.72 +longitude = -119.78 +altitude = 0 +timezone = "America/Los_Angeles" + +[mesh.announce] +enabled = true +interval = 60 + +[[mesh.tools]] +name = "Armooo's MeshView" +url = "https://meshview.armooo.net" + +[[mesh.tools]] +name = "Liam's Meshtastic Map" +url = "https://meshtastic.liamcottle.net" + +[[mesh.tools]] +name = "MeshMap" +url = "https://meshmap.net" + +[[mesh.tools]] +name = "Bay Mesh Explorer" +url = "https://app.bayme.sh" + +[[mesh.tools]] +name = "HWT Path Profiler" +url = "https://heywhatsthat.com/profiler.html" + +[broker] +enabled = true +host = "mqtt.meshtastic.org" +port = 1883 +username = "meshdev" +password = "large4cats" +client_id_prefix = "meshinfo-dev" +topics = [ + "msh/US/CA/SacValley/#", + "msh/US/CA/sacvalley/#", + "msh/US/CA/centralvalley/#", + "msh/US/CA/sandiegocounty/#", + "msh/US/CA/socalmesh/#", +] + +[[broker.topic_tags]] +prefix = "public/msh/" +tag = "public" +label = "Public MQTT" + +[[broker.topic_tags]] +prefix = "msh/US/CA/centralvalley/" +tag = "centralvalley" +label = "Central Valley" + +[[broker.topic_tags]] +prefix = "msh/US/CA/socalmesh/" +tag = "socalmesh" +label = "SoCalMesh" + +[[broker.topic_tags]] +prefix = "msh/US/CA/sandiegocounty/" +tag = "sandiegocounty" +label = "San Diego County" + +[[broker.topic_tags]] +prefix = "msh/US/CA/sacvalley/" +tag = "sacvalley" +label = "Sac Valley" + +[broker.decoders.protobuf] +enabled = true + +[broker.decoders.json] +enabled = true + +[broker.channels] +display = ["0", "1", "2", "3", "4", "5", "6", "7", "8"] + +[[broker.channels.encryption]] +key = "1PG7OiApB1nwvP+rz05pAQ==" +key_name = "Default" + +[broker.channels.meta.0] +label = "MediumFast" +short = "MF" +preset = "MediumFast" +description = "Primary mesh channel" + +[broker.channels.meta.8] +label = "LongFast (legacy)" +short = "LF" +preset = "LongFast" +description = "Legacy / interop channel" + +[broker.channels.meta.1] +label = "Channel 1" +short = "1" + +[broker.channels.meta.2] +label = "Channel 2" +short = "2" + +[broker.channels.meta.3] +label = "Channel 3" +short = "3" + +[broker.channels.meta.4] +label = "Channel 4" +short = "4" + +[broker.channels.meta.5] +label = "Channel 5" +short = "5" + +[broker.channels.meta.6] +label = "Channel 6" +short = "6" + +[broker.channels.meta.7] +label = "Channel 7" +short = "7" + +[[broker.channels.views]] +id = "mf" +label = "MediumFast" +short = "MF" +channels = ["0"] + +[[broker.channels.views]] +id = "lf" +label = "LongFast" +short = "LF" +channels = ["8"] +default = true + +[[broker.channels.views]] +id = "all" +label = "All Channels" +short = "ALL" +channels = ["0", "1", "2", "3", "4", "5", "6", "7", "8"] + +[paths] +backups = "output/backups" +data = "output/data" +output = "output/static-html" +templates = "templates" + +[server] +node_id = "c7181daf" +base_url = "REPLACE_WITH_THE_URL_OF_THIS_SERVER_WITHOUT_TRAILING_SLASH" +node_activity_prune_threshold = 259200 +timezone = "America/Los_Angeles" + +[server.intervals] +data_save = 300 +render = 5 + +[server.backups] +enabled = true +interval = 86400 +max_backups = 7 + +[server.enrich] +enabled = true +interval = 900 +provider = "world.meshinfo.network" + +[server.graph] +enabled = true +max_depth = 10 + +[integrations.discord] +enabled = false +token = "REPLACE_WITH_TOKEN" +guild = "REPLACE_WITH_GUILD_ID" + +[integrations.geocoding] +enabled = false +provider = "geocode.maps.co" + +[integrations.geocoding."geocode.maps.co"] +api_key = "REPLACE_WITH_API_KEY" + +# PostgreSQL is REQUIRED. JSON storage is deprecated and will be removed +# in the next version. +[storage] +read_from = "postgres" +write_to = ["json", "postgres"] + +[storage.postgres] +enabled = true +host = "postgres" +port = 5432 +database = "meshinfo" +username = "postgres" +password = "password" +min_pool_size = 1 +max_pool_size = 5 diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index cacbb29a..d84e15c0 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -56,7 +56,7 @@ services: context: . dockerfile: Dockerfile volumes: - - ./config.json:/app/config.json + - ./config.toml:/app/config.toml - ./output:/app/output - ./templates:/app/templates - ./spa:/app/spa diff --git a/docker-compose.yml b/docker-compose.yml index fb5262a1..35799fa3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,7 +29,7 @@ services: meshinfo: image: ghcr.io/meshaddicts/meshinfo:latest volumes: - - ./config.json:/app/config.json + - ./config.toml:/app/config.toml - ./output:/app/output - ./postgres:/app/postgres:ro environment: diff --git a/memory_data_store.py b/memory_data_store.py index 4fbb8ac3..a3a77c2e 100644 --- a/memory_data_store.py +++ b/memory_data_store.py @@ -369,7 +369,8 @@ async def backup(self): os.makedirs(tmp_path, exist_ok=True) shutil.copytree("output/data", f"{tmp_path}/data") shutil.copytree("output/static-html", f"{tmp_path}/static-html") - shutil.copyfile("config.json", f"{tmp_path}/config.json") + config_file = "config.toml" if os.path.exists("config.toml") else "config.json" + shutil.copyfile(config_file, f"{tmp_path}/{config_file}") shutil.make_archive( base_name, diff --git a/scripts/migrate_json_to_postgres.py b/scripts/migrate_json_to_postgres.py index f5e07e83..86cca1b2 100644 --- a/scripts/migrate_json_to_postgres.py +++ b/scripts/migrate_json_to_postgres.py @@ -438,14 +438,14 @@ async def main(): try: config = Config.load() except Exception as e: - logger.error(f"Failed to load config.json: {e}") - logger.error("Make sure config.json exists and is valid") + logger.error(f"Failed to load config: {e}") + logger.error("Make sure config.toml (or config.json) exists and is valid") return 1 # Check if PostgreSQL is configured pg_config = config.get('storage', {}).get('postgres', {}) if not pg_config.get('enabled', False): - logger.error("PostgreSQL is not enabled in config.json") + logger.error("PostgreSQL is not enabled in config") logger.error("Set storage.postgres.enabled to true before running migration") return 1 diff --git a/tests/test_assets_present.py b/tests/test_assets_present.py index 868a992b..2be527cd 100644 --- a/tests/test_assets_present.py +++ b/tests/test_assets_present.py @@ -3,6 +3,6 @@ def test_required_files_exist(): repo_root = Path(__file__).resolve().parents[1] - for rel in ["banner", "version.json", "config.json.sample"]: + for rel in ["banner", "version.json", "config.json.sample", "config.toml.sample"]: p = repo_root / rel assert p.exists(), f"Missing required file: {rel}" diff --git a/tests/test_config_sample_is_valid_json.py b/tests/test_config_sample_is_valid_json.py index 48952e32..6f8c3528 100644 --- a/tests/test_config_sample_is_valid_json.py +++ b/tests/test_config_sample_is_valid_json.py @@ -1,10 +1,19 @@ import json +import tomllib from pathlib import Path -def test_config_sample_is_valid_json(): +def test_config_toml_sample_is_valid(): + repo_root = Path(__file__).resolve().parents[1] + p = repo_root / "config.toml.sample" + + assert p.exists(), "config.toml.sample missing" + with open(p, "rb") as f: + tomllib.load(f) + +def test_config_json_sample_is_valid(): + """Legacy JSON sample should remain valid while we support both formats.""" repo_root = Path(__file__).resolve().parents[1] p = repo_root / "config.json.sample" - # If some forks rename it, don't hard-fail; but for upstream, it should exist. assert p.exists(), "config.json.sample missing" json.loads(p.read_text(encoding="utf-8")) From 418f3505922d2113897d91ebd0ec31de1519307d Mon Sep 17 00:00:00 2001 From: SimmerV Date: Thu, 5 Mar 2026 22:47:41 -0800 Subject: [PATCH 2/4] fix check --- config.py | 4 ++-- memory_data_store.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config.py b/config.py index 8f11d92d..34ffda63 100644 --- a/config.py +++ b/config.py @@ -499,9 +499,9 @@ def load(cls) -> dict: validate, and return the final config dict. """ # Load user config: prefer TOML, fall back to JSON - if os.path.exists("config.toml"): + if os.path.isfile("config.toml"): user_config = cls._load_toml("config.toml") - elif os.path.exists("config.json"): + elif os.path.isfile("config.json"): logger.warning( "Loading config.json (JSON format is deprecated). " "Please migrate to config.toml. See config.toml.sample for the format." diff --git a/memory_data_store.py b/memory_data_store.py index a3a77c2e..d5a350de 100644 --- a/memory_data_store.py +++ b/memory_data_store.py @@ -369,7 +369,7 @@ async def backup(self): os.makedirs(tmp_path, exist_ok=True) shutil.copytree("output/data", f"{tmp_path}/data") shutil.copytree("output/static-html", f"{tmp_path}/static-html") - config_file = "config.toml" if os.path.exists("config.toml") else "config.json" + config_file = "config.toml" if os.path.isfile("config.toml") else "config.json" shutil.copyfile(config_file, f"{tmp_path}/{config_file}") shutil.make_archive( From 020f31f93a526356f313372e6e015efd1624f0b7 Mon Sep 17 00:00:00 2001 From: SimmerV Date: Fri, 6 Mar 2026 19:29:38 -0800 Subject: [PATCH 3/4] fixes --- config.py | 6 +++--- ...onfig_sample_is_valid_json.py => test_config_samples.py} | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename tests/{test_config_sample_is_valid_json.py => test_config_samples.py} (100%) diff --git a/config.py b/config.py index 34ffda63..f182c014 100644 --- a/config.py +++ b/config.py @@ -569,8 +569,7 @@ def _load_json(cls, path: str) -> dict: return json.load(f) except FileNotFoundError: raise ConfigValidationError( - f"Config file '{path}' not found. " - f"Copy config.toml.sample to config.toml and edit it for your deployment." + f"Config file '{path}' not found." ) except json.JSONDecodeError as e: raise ConfigValidationError( @@ -580,7 +579,8 @@ def _load_json(cls, path: str) -> dict: @classmethod def load_from_file(cls, path: str) -> dict: """Load a config file by extension (TOML or JSON).""" - if ".toml" in path: + from pathlib import Path + if Path(path).suffix == ".toml": return cls._load_toml(path) return cls._load_json(path) diff --git a/tests/test_config_sample_is_valid_json.py b/tests/test_config_samples.py similarity index 100% rename from tests/test_config_sample_is_valid_json.py rename to tests/test_config_samples.py From db2b3e6592ba952611424cac76d5b9b266680937 Mon Sep 17 00:00:00 2001 From: SimmerV Date: Fri, 6 Mar 2026 19:45:08 -0800 Subject: [PATCH 4/4] update .sample comments --- config.toml.sample | 71 +++++++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 19 deletions(-) diff --git a/config.toml.sample b/config.toml.sample index 4a109e5e..6a716f72 100644 --- a/config.toml.sample +++ b/config.toml.sample @@ -1,8 +1,12 @@ # MeshInfo Configuration # Copy this file to config.toml and edit it for your deployment. +# Full documentation: https://github.com/MeshAddicts/meshinfo +# Enable verbose debug logging debug = false +# Mesh Network Metadata +# Describes your mesh network; shown in the web UI and used for announcements. [mesh] name = "Central Valley Mesh" shortname = "CVM" @@ -14,13 +18,15 @@ region = "California" metro = "Fresno, Modesto, Bakersfield" latitude = 36.72 longitude = -119.78 -altitude = 0 -timezone = "America/Los_Angeles" +altitude = 0 # meters above sea level +timezone = "America/Los_Angeles" # IANA timezone (e.g. "UTC", "Europe/London") +# Announce this MeshInfo instance to the mesh network discovery service [mesh.announce] enabled = true -interval = 60 +interval = 60 # minutes +# External tools shown in the web UI sidebar [[mesh.tools]] name = "Armooo's MeshView" url = "https://meshview.armooo.net" @@ -41,13 +47,17 @@ url = "https://app.bayme.sh" name = "HWT Path Profiler" url = "https://heywhatsthat.com/profiler.html" +# MQTT Broker +# Connects to an MQTT broker to receive Meshtastic mesh traffic. [broker] enabled = true host = "mqtt.meshtastic.org" port = 1883 username = "meshdev" password = "large4cats" -client_id_prefix = "meshinfo-dev" +client_id_prefix = "meshinfo-dev" # a random UUID is appended at startup + +# MQTT topic subscriptions (wildcards supported) topics = [ "msh/US/CA/SacValley/#", "msh/US/CA/sacvalley/#", @@ -56,6 +66,7 @@ topics = [ "msh/US/CA/socalmesh/#", ] +# Tags applied to messages based on their topic prefix (used for filtering in the UI) [[broker.topic_tags]] prefix = "public/msh/" tag = "public" @@ -81,19 +92,25 @@ prefix = "msh/US/CA/sacvalley/" tag = "sacvalley" label = "Sac Valley" +# Message decoders (at least one should be enabled) [broker.decoders.protobuf] enabled = true [broker.decoders.json] enabled = true +# Channel Configuration +# Controls which Meshtastic channels are decoded, displayed, and grouped. [broker.channels] +# Which channel indices to show in the UI display = ["0", "1", "2", "3", "4", "5", "6", "7", "8"] +# Encryption keys for decoding channel traffic (base64-encoded) [[broker.channels.encryption]] key = "1PG7OiApB1nwvP+rz05pAQ==" key_name = "Default" +# Channel metadata by index -- labels and presets shown in the UI [broker.channels.meta.0] label = "MediumFast" short = "MF" @@ -134,6 +151,7 @@ short = "6" label = "Channel 7" short = "7" +# Channel views group channels together in the UI (e.g. tabs/filters) [[broker.channels.views]] id = "mf" label = "MediumFast" @@ -145,7 +163,7 @@ id = "lf" label = "LongFast" short = "LF" channels = ["8"] -default = true +default = true # shown by default in the UI [[broker.channels.views]] id = "all" @@ -153,41 +171,54 @@ label = "All Channels" short = "ALL" channels = ["0", "1", "2", "3", "4", "5", "6", "7", "8"] +# File Paths +# Directories for data output and templates relative to the app root. [paths] backups = "output/backups" data = "output/data" output = "output/static-html" templates = "templates" +# Server Settings +# Controls runtime behavior, scheduling intervals, and feature toggles. [server] -node_id = "c7181daf" +node_id = "c7181daf" # central mesh node (needs clarifying/rework) base_url = "REPLACE_WITH_THE_URL_OF_THIS_SERVER_WITHOUT_TRAILING_SLASH" -node_activity_prune_threshold = 259200 +node_activity_prune_threshold = 259200 # seconds (default: 3 days) timezone = "America/Los_Angeles" +# How often periodic tasks run [server.intervals] -data_save = 300 -render = 5 +data_save = 300 # seconds between data saves to disk +render = 5 # seconds between static HTML renders +# Automatic backups of data and config [server.backups] enabled = true -interval = 86400 -max_backups = 7 +interval = 86400 # seconds between backups (default: 24 hours) +max_backups = 7 # number of backup archives to keep +# Node enrichment via external data sources [server.enrich] enabled = true -interval = 900 +interval = 900 # seconds between enrichment runs provider = "world.meshinfo.network" +# Network graph generation [server.graph] enabled = true -max_depth = 10 +max_depth = 10 # maximum hop depth for neighbor graph +# Integrations +# Optional third-party service connections. + +# Discord bot for mesh notifications [integrations.discord] enabled = false token = "REPLACE_WITH_TOKEN" guild = "REPLACE_WITH_GUILD_ID" +# Reverse geocoding (coordinates to human-readable addresses) [integrations.geocoding] enabled = false provider = "geocode.maps.co" @@ -195,18 +226,20 @@ provider = "geocode.maps.co" [integrations.geocoding."geocode.maps.co"] api_key = "REPLACE_WITH_API_KEY" +# Storage # PostgreSQL is REQUIRED. JSON storage is deprecated and will be removed -# in the next version. +# in a future version. + [storage] -read_from = "postgres" -write_to = ["json", "postgres"] +read_from = "postgres" # "postgres" or "json" (deprecated) +write_to = ["json", "postgres"] # backends that receive writes [storage.postgres] enabled = true -host = "postgres" +host = "postgres" # use "localhost" if not using Docker port = 5432 database = "meshinfo" username = "postgres" password = "password" -min_pool_size = 1 -max_pool_size = 5 +min_pool_size = 1 # minimum database connection pool size +max_pool_size = 5 # maximum database connection pool size