diff --git a/README.md b/README.md index 3ec06e6..b1c1951 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ sudo ./install-service.sh 2. Configure the bot: ```bash -sudo nano /opt/meshcore-bot/config.ini +sudo nano /etc/meshcore-bot/config.ini ``` 3. Start the service: @@ -133,7 +133,9 @@ make deb sudo dpkg -i dist/meshcore-bot_*.deb ``` -The package installs the bot to `/opt/meshcore-bot/`, installs a systemd unit, and creates a `meshcore-bot` system user. +The package installs root-owned code in `/opt/meshcore-bot/`, configuration in +`/etc/meshcore-bot/`, mutable state in `/var/lib/meshcore-bot/`, logs in +`/var/log/meshcore-bot/`, and creates a `meshcore-bot` system user. ### Docker Deployment For containerized deployment using Docker: diff --git a/docs/getting-started.md b/docs/getting-started.md index 02355be..c87e667 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -58,7 +58,7 @@ Run the bot as a system service on Linux: ```bash sudo ./install-service.sh -sudo nano /opt/meshcore-bot/config.ini # configure +sudo nano /etc/meshcore-bot/config.ini # configure sudo systemctl start meshcore-bot sudo systemctl status meshcore-bot ``` diff --git a/docs/service-installation.md b/docs/service-installation.md index 5b14f17..2f57973 100644 --- a/docs/service-installation.md +++ b/docs/service-installation.md @@ -5,10 +5,15 @@ This guide explains how to install the MeshCore Bot as a systemd service on Linu ## Prerequisites - Linux system with systemd -- Python 3.7+ (Python 3.12+ recommended; on 3.11 the meshcore dependency has an f-string bug — the install script patches it automatically) +- Python 3.10+ +- `rsync` - Root/sudo access - MeshCore-compatible device +You can install from a Debian package instead of the standalone script. See the +[README](https://github.com/agessaman/meshcore-bot#debian-package-deb) for build +instructions. + ## Quick Installation 1. **Clone and navigate to the bot directory:** @@ -24,7 +29,7 @@ This guide explains how to install the MeshCore Bot as a systemd service on Linu 3. **Configure the bot:** ```bash - sudo nano /opt/meshcore-bot/config.ini + sudo nano /etc/meshcore-bot/config.ini ``` 4. **Start the service:** @@ -37,40 +42,94 @@ This guide explains how to install the MeshCore Bot as a systemd service on Linu sudo systemctl status meshcore-bot ``` +## Upgrading + +After updating the source checkout, run: + +```bash +sudo ./install-service.sh --upgrade +``` + +The upgrader rebuilds the virtual environment, updates the service unit, and migrates +legacy relative paths into the current service layout. It preserves the active +configuration, database, logs, local plugins, and installed-only alternative command +files. If an upgrade fails after stopping an active service, it makes one best-effort +attempt to restart that service while preserving the original failure status. + +Read the [upgrade guide](upgrade.md) before upgrading an existing installation. + ## Manual Installation If you prefer to install manually: ### 1. Create Service User + ```bash sudo useradd --system --no-create-home --shell /bin/false meshcore ``` ### 2. Create Directories + ```bash -sudo mkdir -p /opt/meshcore-bot -sudo mkdir -p /var/log/meshcore-bot +sudo install -d -o root -g root -m 0755 /opt/meshcore-bot +sudo install -d -o meshcore -g meshcore -m 0700 /etc/meshcore-bot +sudo install -d -o meshcore -g meshcore -m 0700 /var/lib/meshcore-bot +sudo install -d -o meshcore -g meshcore -m 0750 /var/log/meshcore-bot ``` ### 3. Copy Bot Files + +This manual copy sequence is for a clean installation. Use the supported upgrader +above for an existing installation so installed-only alternative commands and runtime +state are preserved. + ```bash -sudo cp -r . /opt/meshcore-bot/ -sudo chown -R meshcore:meshcore /opt/meshcore-bot -sudo chown -R meshcore:meshcore /var/log/meshcore-bot +sudo rsync -a --delete \ + --exclude=.git --exclude=venv --exclude=.venv \ + --exclude=config.ini --exclude=local/ \ + ./ /opt/meshcore-bot/ +sudo chown -R root:root /opt/meshcore-bot +sudo chmod -R go-w /opt/meshcore-bot ``` -### 4. Install Service File +Keep custom plugins under `/var/lib/meshcore-bot/local/`; do not make the executable +tree writable by the service account. + +### 4. Create the Virtual Environment + ```bash -sudo cp meshcore-bot.service /etc/systemd/system/ +sudo python3 -m venv /opt/meshcore-bot/venv +sudo /opt/meshcore-bot/venv/bin/pip install \ + -r /opt/meshcore-bot/requirements.txt +``` + +### 5. Install Configuration + +```bash +sudo cp /opt/meshcore-bot/config.ini.example /etc/meshcore-bot/config.ini +sudo chown meshcore:meshcore /etc/meshcore-bot/config.ini +sudo chmod 0600 /etc/meshcore-bot/config.ini +``` + +Set these paths in the active configuration: + +```ini +[Bot] +db_path = /var/lib/meshcore-bot/meshcore_bot.db +local_dir_path = /var/lib/meshcore-bot/local + +[Logging] +log_file = /var/log/meshcore-bot/meshcore_bot.log +``` + +### 6. Install the Service File + +```bash +sudo cp /opt/meshcore-bot/meshcore-bot.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable meshcore-bot ``` -### 5. Install Dependencies -```bash -sudo pip3 install -r /opt/meshcore-bot/requirements.txt -``` - ## Service Management ### Start/Stop/Restart @@ -105,10 +164,10 @@ sudo systemctl disable meshcore-bot # Don't start on boot ## Configuration -The bot configuration is located at `/opt/meshcore-bot/config.ini`. Edit it with: +The active bot configuration is `/etc/meshcore-bot/config.ini`. Edit it with: ```bash -sudo nano /opt/meshcore-bot/config.ini +sudo nano /etc/meshcore-bot/config.ini ``` After changing configuration, you can reload in place (no process restart): @@ -126,9 +185,11 @@ sudo systemctl restart meshcore-bot ## Service Features ### Security + - Runs as dedicated `meshcore` user - No shell access for service user -- Restricted file system access +- Executable code and the virtual environment are root-owned +- Only configuration, state, and log directories are service-writable - Resource limits (512MB RAM, 50% CPU) ### Reliability @@ -147,22 +208,21 @@ sudo systemctl restart meshcore-bot ### Service Won't Start 1. Check service status: `sudo systemctl status meshcore-bot` 2. View logs: `sudo journalctl -u meshcore-bot -n 50` -3. Check configuration: `sudo nano /opt/meshcore-bot/config.ini` -4. Verify dependencies: `sudo pip3 list | grep meshcore` +3. Check configuration: `sudo nano /etc/meshcore-bot/config.ini` +4. Verify dependencies: `/opt/meshcore-bot/venv/bin/pip list | grep meshcore` -### SyntaxError: f-string: unmatched '[' (Python 3.11) -If the bot fails on import with this error in `meshcore/commands/contact.py`, you are on Python 3.11 and the **meshcore** dependency uses an f-string that only works on Python 3.12+. +### Dependency Import or Syntax Errors -**Options:** -- **Recommended:** Use Python 3.12+ (create the venv with `python3.12` if available, then re-run `./install-service.sh --upgrade`). -- **Or:** Re-run the install script so it can patch the installed package: - `sudo ./install-service.sh --upgrade` - The script detects Python 3.11 and patches the meshcore file in the venv. -- **Manual patch:** Edit `/opt/meshcore-bot/venv/lib/python3.11/site-packages/meshcore/commands/contact.py`, find the line containing `contact["adv_name"]` inside the f-string, and change it to `contact['adv_name']` (single quotes around `adv_name`). +Do not patch installed dependencies in place. Update the source checkout and rerun +`sudo ./install-service.sh --upgrade` so the installer creates a fresh virtual +environment from the current requirements. ### Permission Issues + 1. Check file ownership: `ls -la /opt/meshcore-bot/` -2. Fix permissions: `sudo chown -R meshcore:meshcore /opt/meshcore-bot` +2. Confirm code is root-owned and not writable by `meshcore`. +3. Confirm `/etc/meshcore-bot`, `/var/lib/meshcore-bot`, and + `/var/log/meshcore-bot` are owned by `meshcore`. ### Connection Issues 1. Verify device connection (serial port, BLE, etc.) @@ -186,9 +246,12 @@ sudo ./uninstall-service.sh This will: - Stop and disable the service - Remove systemd service file -- Remove installation directory -- Remove log directory -- Remove service user +- Optionally remove the installation and log directories +- Optionally remove the service user + +The current uninstaller preserves `/etc/meshcore-bot` and `/var/lib/meshcore-bot`. +Back up and remove those directories separately if you want to erase all configuration +and state. ## File Locations @@ -196,8 +259,9 @@ This will: |-----------|----------| | Service file | `/etc/systemd/system/meshcore-bot.service` | | Bot files | `/opt/meshcore-bot/` | -| Configuration | `/opt/meshcore-bot/config.ini` | -| Logs | `/var/log/meshcore-bot/` (if configured) | +| Configuration | `/etc/meshcore-bot/config.ini` | +| Database and local plugins | `/var/lib/meshcore-bot/` | +| Logs | `/var/log/meshcore-bot/` | | System logs | `journalctl -u meshcore-bot` | ## Advanced Configuration @@ -228,5 +292,5 @@ Environment=CUSTOM_VAR=value For issues with the service installation: 1. Check the logs: `sudo journalctl -u meshcore-bot -f` -2. Verify configuration: `sudo nano /opt/meshcore-bot/config.ini` +2. Verify configuration: `sudo nano /etc/meshcore-bot/config.ini` 3. Test manually: `sudo -u meshcore python3 /opt/meshcore-bot/meshcore_bot.py` diff --git a/docs/upgrade.md b/docs/upgrade.md index 3ff5ed5..f2833ad 100644 --- a/docs/upgrade.md +++ b/docs/upgrade.md @@ -2,6 +2,74 @@ This document describes changes that may affect users upgrading from previous versions. +## Upgrading from v0.9.3 to v0.9.4 + +### Service Layout and Ownership + +The standalone installer and Debian package now keep executable code root-owned and +separate mutable runtime data: + +| Component | Linux | macOS | +|-----------|-------|-------| +| Code and virtual environment | `/opt/meshcore-bot` | `/usr/local/meshcore-bot` | +| Configuration | `/etc/meshcore-bot/config.ini` | `/usr/local/etc/meshcore-bot/config.ini` | +| Database and local plugins | `/var/lib/meshcore-bot` | `/usr/local/var/lib/meshcore-bot` | +| Logs | `/var/log/meshcore-bot` | `/usr/local/var/log/meshcore-bot` | + +Run `sudo ./install-service.sh --upgrade` from an updated source checkout. The +installer requires Python 3.10+ and `rsync`. It stops an active service before the +database migration, copies relative-path databases coherently, rewrites the migrated +configuration, builds a fresh virtual environment, and restarts a service that was +active before the upgrade. + +Before upgrading, keep a separate backup of your configuration and database. The +installer preserves: + +- the active configuration and absolute custom paths; +- relative-path databases, logs, and the `local/` plugin tree; +- installed-only files under `modules/commands/alternatives/`. + +Trusted source versions still replace shipped alternative commands. If the upgrade +fails after stopping an active service, the installer attempts one best-effort restart +and retains the original failure exit status. Inspect the reported error before +retrying. + +### Configuration Compatibility + +v0.9.3 shipped configuration examples pass the v0.9.4 strict validator. Normal startup +warns about unknown sections or keys without refusing to start. You can check a config +before upgrading: + +```bash +python3 validate_config.py --config /path/to/config.ini --strict +``` + +### Outbound HTTP and Feeds + +Outbound HTTP now validates every resolved address and redirect. Private feed URLs +remain disabled by default; set `allow_private_urls = true` only for intentional +private-network feeds. Cloud metadata and non-unicast destinations remain blocked. +Feed responses are bounded by `max_response_bytes` and `max_parsed_items`. + +### Optional Geocoding Data + +`pycountry` and `us` remain optional under the `geo` extra. Install them for improved +country and US-state normalization: + +```bash +pip install -e ".[geo]" +``` + +The standalone installer also offers the geocoding extras interactively. + +## Upgrading from v0.8 to v0.9 + +- Python 3.10+ is required. +- The global `[Aliases]` section was replaced by per-command `aliases =` keys. +- Database migrations run automatically at startup. +- Review web-viewer authentication before binding beyond localhost. +- Connection and radio settings still require a process restart after changes. + ## Upgrading from v0.7 ### Config Compatibility diff --git a/tests/test_service_packaging.py b/tests/test_service_packaging.py index 945debd..2cde038 100644 --- a/tests/test_service_packaging.py +++ b/tests/test_service_packaging.py @@ -67,6 +67,26 @@ def test_standalone_installer_separates_code_and_private_state(): assert "Previously active service was restarted after the failed upgrade" in installer +def test_service_documentation_matches_hardened_layout() -> None: + readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8") + getting_started = (REPO_ROOT / "docs/getting-started.md").read_text(encoding="utf-8") + service_docs = (REPO_ROOT / "docs/service-installation.md").read_text( + encoding="utf-8" + ) + upgrade_docs = (REPO_ROOT / "docs/upgrade.md").read_text(encoding="utf-8") + + for text in (readme, getting_started, service_docs): + assert "sudo nano /opt/meshcore-bot/config.ini" not in text + assert "sudo nano /etc/meshcore-bot/config.ini" in text + + assert "Python 3.10+" in service_docs + assert "`rsync`" in service_docs + assert "sudo chown -R meshcore:meshcore /opt/meshcore-bot" not in service_docs + assert "patches the meshcore file" not in service_docs + assert "/var/lib/meshcore-bot" in service_docs + assert "Upgrading from v0.9.3 to v0.9.4" in upgrade_docs + + def test_service_sync_preserves_only_installed_only_alternatives(tmp_path: Path) -> None: source = tmp_path / "source" installed = tmp_path / "installed"