diff --git a/README-Docker.md b/README-Docker.md index af29cda..65f8a9c 100644 --- a/README-Docker.md +++ b/README-Docker.md @@ -1,96 +1,75 @@ # Running MeshView with Docker MeshView container images are built automatically and published to GitHub Container Registry. +The examples below use a Bash-compatible shell and Docker Compose v2 (`docker compose`). ## Quick Start -Pull and run the latest image: +Prepare the configuration and persistent directories before starting the container: ```bash docker pull ghcr.io/pablorevilla-meshtastic/meshview:latest - -docker run -d \ - --name meshview \ - -p 8081:8081 \ - -v ./config:/etc/meshview \ - -v ./data:/var/lib/meshview \ - -v ./logs:/var/log/meshview \ - ghcr.io/pablorevilla-meshtastic/meshview:latest -``` - -Access the web interface at: http://localhost:8081 - -## Volume Mounts - -The container uses three volumes for persistent data: - -| Volume | Purpose | Required | -|--------|---------|----------| -| `/etc/meshview` | Configuration files | Yes | -| `/var/lib/meshview` | Database storage | Recommended | -| `/var/log/meshview` | Log files | Optional | - -### Configuration Volume - -Mount a directory containing your `config.ini` file: - -```bash --v /path/to/your/config:/etc/meshview -``` - -If no config is provided, the container will use the default `sample.config.ini`. - -### Database Volume - -Mount a directory to persist the SQLite database: - -```bash --v /path/to/your/data:/var/lib/meshview -``` - -**Important:** Without this mount, your database will be lost when the container stops. - -### Logs Volume - -Mount a directory to access logs from the host: - -```bash --v /path/to/your/logs:/var/log/meshview -``` - -## Complete Example - -Create a directory structure and run: - -```bash -# Create directories mkdir -p meshview/{config,data,logs,backups} -# Copy sample config (first time only) -docker run --rm ghcr.io/pablorevilla-meshtastic/meshview:latest \ - cat /etc/meshview/config.ini > meshview/config/config.ini +# First-time setup only: extract the image's bundled configuration. +# --entrypoint bypasses the normal MeshView startup command. +docker run --rm --entrypoint cat \ + ghcr.io/pablorevilla-meshtastic/meshview:latest \ + /etc/meshview/config.ini > meshview/config/config.ini -# Edit config.ini with your MQTT settings +# Edit MQTT settings, site settings, and backup options before starting. nano meshview/config/config.ini -# Run the container +# On Linux, give the container user (UID 10001) ownership of writable data. +sudo chown -R 10001 meshview/data meshview/logs meshview/backups +sudo chmod -R u+rwX meshview/data meshview/logs meshview/backups +# The configuration stays owned by you and must be readable by the container. +chmod 755 meshview/config +chmod 644 meshview/config/config.ini + docker run -d \ --name meshview \ --restart unless-stopped \ -p 8081:8081 \ - -v $(pwd)/meshview/config:/etc/meshview \ - -v $(pwd)/meshview/data:/var/lib/meshview \ - -v $(pwd)/meshview/logs:/var/log/meshview \ + -v "$(pwd)/meshview/config:/etc/meshview:ro" \ + -v "$(pwd)/meshview/data:/var/lib/meshview" \ + -v "$(pwd)/meshview/logs:/var/log/meshview" \ + -v "$(pwd)/meshview/backups:/var/lib/meshview/backups" \ ghcr.io/pablorevilla-meshtastic/meshview:latest ``` +Run the config extraction only for a new installation: the redirection overwrites any +existing `config.ini`. On Docker Desktop, host file permissions may be managed differently; +the requirement is that UID 10001 can read the configuration and write data, logs, and backups. + +Access the web interface at http://localhost:8081. Check startup with `docker logs -f meshview`. +Database migrations run automatically when MeshView starts. + +## Volume Mounts + +| Container path | Purpose | Host directory in the examples | +|----------------|---------|--------------------------------| +| `/etc/meshview` | Read-only configuration containing `config.ini` | `meshview/config` | +| `/var/lib/meshview` | SQLite database storage | `meshview/data` | +| `/var/log/meshview` | File logs, when enabled | `meshview/logs` | +| `/var/lib/meshview/backups` | Database backups, when enabled | `meshview/backups` | + +An empty host configuration directory hides the image's bundled `config.ini` and prevents +startup. There is no fallback when a mounted configuration directory lacks that file. +If you omit the configuration mount entirely, the image's bundled configuration is used. + +Stopping or restarting a container does not delete the database. Explicit host mounts make +it straightforward to preserve and reuse data when removing or recreating containers. +Without explicit mounts, the image declares anonymous volumes; do not rely on those being +automatically reused by a replacement container, and do not delete them if you need their data. + ## Docker Compose -Create a `docker-compose.yml`: +Use the preparation steps from Quick Start, but skip its `docker run -d` command. +Create `compose.yaml` **inside the `meshview` directory**, alongside `config`, `data`, +`logs`, and `backups`: ```yaml -version: '3.8' - services: meshview: image: ghcr.io/pablorevilla-meshtastic/meshview:latest @@ -99,45 +78,57 @@ services: ports: - "8081:8081" volumes: - - ./config:/etc/meshview + - ./config:/etc/meshview:ro - ./data:/var/lib/meshview - ./logs:/var/log/meshview - - ./backups:/var/lib/meshview/backups # For database backups + - ./backups:/var/lib/meshview/backups environment: - - TZ=America/Los_Angeles # Set your timezone + TZ: America/Los_Angeles ``` -Run with: +Start from that directory: ```bash -docker-compose up -d +cd meshview +docker compose up -d +docker compose logs -f ``` +Choose either Compose or `docker run` to manage the installation. If switching from the +Quick Start container to Compose, stop and remove the old container first, keeping its +host directories: `docker stop meshview` followed by `docker rm meshview`. + ## Configuration -### Minimum Configuration - -Edit your `config.ini` to configure MQTT connection: +Edit the extracted `config.ini` rather than replacing it with a partial configuration. +Keep the container server listening on all interfaces: ```ini -[mqtt] -server = mqtt.meshtastic.org -topics = ["msh/US/#"] -port = 1883 -username = -password = +[server] +bind = 0.0.0.0 +port = 8081 +``` +Set your MQTT broker, topics, and credentials in the existing `[mqtt]` section. The broker +must be reachable from the container; `localhost` refers to the container itself. + +Keep the database path inside the mounted data directory: + +```ini [database] -# SQLAlchemy async connection string. -# Examples: -# sqlite+aiosqlite:///var/lib/meshview/packets.db -# postgresql+asyncpg://user:pass@host:5432/meshview connection_string = sqlite+aiosqlite:////var/lib/meshview/packets.db ``` +The four slashes after `sqlite+aiosqlite:` specify an absolute Linux path. PostgreSQL +is also supported with a connection string such as +`postgresql+asyncpg://user:pass@host:5432/meshview`. + +After configuration changes, restart the application with `docker restart meshview`, or +`docker compose restart meshview` for a Compose installation. + ### Database Backups -To enable automatic daily backups inside the container: +Enable backups in the existing `[cleanup]` section: ```ini [cleanup] @@ -147,99 +138,123 @@ backup_hour = 2 backup_minute = 00 ``` -Then mount the backups directory: - -```bash --v $(pwd)/meshview/backups:/var/lib/meshview/backups -``` +Both startup examples mount this directory to `meshview/backups` on the host. Backups +remain disabled until you enable them in the configuration. ## Available Tags | Tag | Description | |-----|-------------| -| `latest` | Latest build from the main branch | -| `dev-v3` | Development branch | -| `v1.2.3` | Specific version tags | +| `latest` | Latest build from the repository's default branch | +| Branch name | Builds published for that branch | +| `3.0.6` | Example version tag; use a version published in the registry | ## Updating -Pull the latest image and restart: +For a Compose installation, run from the directory containing `compose.yaml`: + +```bash +docker compose pull +docker compose up -d +``` + +For a `docker run` installation, run from the same parent directory used in Quick Start: ```bash docker pull ghcr.io/pablorevilla-meshtastic/meshview:latest -docker restart meshview +docker stop meshview +docker rm meshview + +docker run -d \ + --name meshview \ + --restart unless-stopped \ + -p 8081:8081 \ + -v "$(pwd)/meshview/config:/etc/meshview:ro" \ + -v "$(pwd)/meshview/data:/var/lib/meshview" \ + -v "$(pwd)/meshview/logs:/var/log/meshview" \ + -v "$(pwd)/meshview/backups:/var/lib/meshview/backups" \ + ghcr.io/pablorevilla-meshtastic/meshview:latest ``` -Or with docker-compose: - -```bash -docker-compose pull -docker-compose up -d -``` +Preserve any custom ports, mounts, or environment settings when recreating the container. +Do not repeat the first-time configuration extraction during updates. Pulling an image +and merely restarting an existing container does not switch it to the new image. ## Logs -View container logs: - ```bash docker logs meshview - -# Follow logs docker logs -f meshview - -# Last 100 lines docker logs --tail 100 meshview ``` +For Compose installations, `docker compose logs -f meshview` also works. + ## Troubleshooting ### Container won't start -Check logs: -```bash -docker logs meshview -``` +Check `docker logs meshview`. Verify that the mounted `config` directory contains a +readable `config.ini`, and that the database and log directories are writable by UID 10001. + +### Docker Desktop reports mounts denied + +Make sure the installation directory is shared with Docker Desktop under Settings → +Resources → File Sharing. Bind-mount source paths must exist on the Docker daemon's +host; a remote daemon cannot mount directories that exist only on your client. ### Database permission issues -Ensure the data directory is writable: +From the parent directory containing `meshview`, repair writable directory ownership: + ```bash -chmod -R 755 meshview/data +sudo chown -R 10001 meshview/data meshview/logs meshview/backups +sudo chmod -R u+rwX meshview/data meshview/logs meshview/backups ``` +Setting permissions to `755` alone does not let UID 10001 write to directories owned +by another user. SQLite also needs to create journal/WAL files beside the database. + ### Can't connect to MQTT -1. Check your MQTT configuration in `config.ini` -2. Verify network connectivity from the container: - ```bash - docker exec meshview ping mqtt.meshtastic.org - ``` +Check the broker hostname, port, credentials, and topics in `config.ini`. +Test DNS resolution and a TCP connection using Python, which is included in the image: + +```bash +# Replace the hostname and port with your configured broker. +docker exec meshview /opt/venv/bin/python -c \ + 'import socket; s = socket.create_connection(("mqtt.meshtastic.org", 1883), timeout=10); print("TCP connection successful"); s.close()' +``` + +This checks network connectivity, not MQTT authentication or topic permissions. +The image does not include `ping`. ### Port already in use -Change the host port (left side): +Change the host port (left side) to `8082`, keeping the container port at `8081`: + ```bash -p 8082:8081 ``` -Then access at: http://localhost:8082 +For Compose, use `"8082:8081"` under `ports`. Access http://localhost:8082. ## Building Your Own Image -If you want to build from source: - ```bash git clone https://github.com/pablorevilla-meshtastic/meshview.git cd meshview docker build -f Containerfile -t meshview:local . ``` +Use `meshview:local` instead of the registry image in the setup and run commands. + ## Security Notes -- The container runs as a non-root user (`app`, UID 10001) -- No privileged access required -- Only port 8081 is exposed -- All data stored in mounted volumes +- The container runs as a non-root user (`app`, UID 10001). +- No privileged access is required. +- The examples publish port 8081 on the host; configuration is mounted read-only. +- Database, file logs, and backups use the mounted directories shown above. ## Support diff --git a/meshview/templates/stats.html b/meshview/templates/stats.html index 4e93006..6763729 100644 --- a/meshview/templates/stats.html +++ b/meshview/templates/stats.html @@ -328,7 +328,8 @@ async function loadAltitudeNodes(refreshNodes = false) { axisLabel:{interval:0, rotate:35, width:85, overflow:"truncate"}}, yAxis: {type:"value", name:"Altitude (m above sea level)", nameGap:30}, series: [{type:"bar", name:"Altitude", barMaxWidth:45, - itemStyle:{color:"#187346"}, label:{show:true, position:"top", formatter:"{c} m"}, + itemStyle:{color:"#187346"}, + label:{show:true, position:"top", formatter:"{c} m", color:"#243247", textBorderWidth:0}, data:altitudeRows.map(node => node.last_altitude)}] }, true); status.textContent = altitudeRows.length