feat(docs): update Raspberry Pi installation guide for MeshChatX with automated setup scripts and detailed service configuration instructions

This commit is contained in:
Ivan
2026-04-16 20:14:35 -05:00
parent 09d0fc70d9
commit b6a8ae9751
3 changed files with 872 additions and 112 deletions
+189 -56
View File
@@ -1,99 +1,232 @@
# MeshChat on a Raspberry Pi
# MeshChatX on Raspberry Pi
A simple guide to install [MeshChat](https://github.com/liamcottle/reticulum-meshchat) on a Raspberry Pi.
This guide shows a simple headless setup for running MeshChatX on a Raspberry Pi 4
with a web UI you can access from another device on your network.
This would allow you to connect an [RNode](https://github.com/markqvist/RNode_Firmware) (such as a Heltec v3) to the Rasbperry Pi via USB, and then access the MeshChat Web UI from another machine on your network.
This install path uses a release wheel, which already includes frontend assets.
My intended use case is to run the Pi + RNode combo from my solar-powered shed, and access the MeshChat Web UI via WiFi.
## Automated Setup Scripts
> Note: This has been tested on a Raspberry Pi 4 Model B
## Install Raspberry Pi OS
If you haven't already done so, the first step is to install Raspberry Pi OS onto an sdcard, and then boot up the Pi. Once booted, follow the below commands.
## Update System
If you want one-command setup, use the interactive installer from repo root:
```bash
bash scripts/rpi/install_meshchatx.sh
```
The installer guides you through:
- Optional `espeak-ng` install (tries apt/dnf/pacman)
- Install method (`pipx` or `venv + pip`)
- Storage and Reticulum directories
- Bind host and port (with availability check)
- HTTPS on/off (default on)
- Service mode (`system`, `user`, or `none`)
- Service startup verification (`Running on ...`)
If startup validation fails, it prints recent logs and stops the service to avoid
restart loops.
The installer also applies compatibility dependencies needed by older wheel
releases.
For all options:
```bash
bash scripts/rpi/install_meshchatx.sh --help
```
## 1) Install Base Dependencies
```bash
sudo apt update
sudo apt upgrade
sudo apt upgrade -y
sudo apt install -y python3 python3-pip pipx
```
## Install System Dependencies
## 2) Enable pipx Path
```
sudo apt install git
sudo apt install python3-pip
```bash
pipx ensurepath
source ~/.profile
```
## Install NodeJS v24
If `pipx` is not available in your distro package repo, install it with:
```
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/nodesource.gpg
NODE_MAJOR=24
echo "deb [signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
sudo apt update
sudo apt install nodejs
```bash
python3 -m pip install --user pipx
python3 -m pipx ensurepath
source ~/.profile
```
## Install pnpm
## 3) Install MeshChatX with pipx (recommended)
```
corepack enable
corepack prepare pnpm@latest --activate
Preferred option (recommended): install from a release wheel (4.4.0 or newer),
because the wheel bundles frontend assets.
```bash
pipx install /path/to/reticulum_meshchatx-<version>-py3-none-any.whl
```
## Install MeshChat
Direct example (v4.4.0):
```
git clone https://github.com/liamcottle/reticulum-meshchat
cd reticulum-meshchat
pip install -r requirements.txt --break-system-packages
pnpm install --prod
pnpm run build-frontend
```bash
pipx install "https://git.quad4.io/RNS-Things/MeshChatX/releases/download/v4.4.0/reticulum_meshchatx-4.4.0-py3-none-any.whl"
```
## Run MeshChat
`py3-none-any` wheels are architecture-independent, so the same wheel artifact
works on Raspberry Pi ARM and x86_64 Linux systems.
```
python meshchat.py --headless --host 0.0.0.0
Upgrade example:
```bash
pipx upgrade meshchatx
```
## Configure Service
## 4) Install MeshChatX without pipx (venv + pip)
Adding a `systemd` service will allow MeshChat to run in the background when you disconnect from the Pi's terminal.
If you prefer not to use pipx:
```
sudo nano /etc/systemd/system/reticulum-meshchat.service
```bash
mkdir -p ~/meshchatx
cd ~/meshchatx
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install "https://git.quad4.io/RNS-Things/MeshChatX/releases/download/v4.4.0/reticulum_meshchatx-4.4.0-py3-none-any.whl"
```
Run command in venv mode:
```bash
~/meshchatx/.venv/bin/meshchatx --headless --host 0.0.0.0 --port 8000
```
## 5) Run MeshChatX (Headless)
```bash
meshchatx --headless --host 0.0.0.0 --port 8000
```
Then open:
```text
http://<pi-ip>:8000
```
## 6) Configure a systemd Service
`systemd` keeps MeshChatX running in the background and starts it automatically
on boot.
You have two service styles:
- System service (`/etc/systemd/system/...`) for always-on host services.
- User service (`~/.config/systemd/user/...`) for per-user sessions.
### Option A: System service (recommended for Pi node/server use)
Create `/etc/systemd/system/meshchatx.service`:
```ini
[Unit]
Description=reticulum-meshchat
After=network.target
StartLimitIntervalSec=0
Description=MeshChatX Headless (system service)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=pi
Group=pi
WorkingDirectory=/home/pi/meshchatx
Environment="PATH=/home/pi/.local/bin:/usr/bin:/bin"
ExecStart=/home/pi/.local/bin/meshchatx --headless --host 0.0.0.0 --port 8000 --storage-dir /home/pi/meshchatx/storage --reticulum-config-dir /home/pi/.reticulum
Restart=always
RestartSec=1
User=liamcottle
Group=liamcottle
WorkingDirectory=/home/liamcottle/reticulum-meshchat
ExecStart=/usr/bin/env python /home/liamcottle/reticulum-meshchat/meshchat.py --headless --host 0.0.0.0
RestartSec=3
[Install]
WantedBy=multi-user.target
```
> Note: Make sure to update the usernames in the service file if needed.
The above service file is for pipx installs. For venv installs, use:
```
sudo systemctl enable reticulum-meshchat.service
sudo systemctl start reticulum-meshchat.service
sudo systemctl status reticulum-meshchat.service
```ini
[Service]
Type=simple
User=pi
Group=pi
WorkingDirectory=/home/pi/meshchatx
Environment="PATH=/home/pi/meshchatx/.venv/bin:/usr/bin:/bin"
ExecStart=/home/pi/meshchatx/.venv/bin/meshchatx --headless --host 0.0.0.0 --port 8000 --storage-dir /home/pi/meshchatx/storage --reticulum-config-dir /home/pi/.reticulum
Restart=always
RestartSec=3
```
You should now be able to access MeshChat via your Pi's IP address.
Update `User`, `Group`, and paths if your install location is different.
> Note: Don't forget to include the default port `8000`
Enable and start:
```bash
mkdir -p /home/pi/meshchatx/storage /home/pi/.reticulum
sudo chown -R pi:pi /home/pi/meshchatx
sudo systemctl daemon-reload
sudo systemctl enable --now meshchatx.service
sudo systemctl status meshchatx.service
```
### Option B: User service (no sudo system unit)
Create `~/.config/systemd/user/meshchatx.service`:
```ini
[Unit]
Description=MeshChatX Headless (user service)
After=network-online.target
[Service]
Type=simple
WorkingDirectory=%h/meshchatx
Environment="PATH=%h/.local/bin:/usr/bin:/bin"
ExecStart=%h/.local/bin/meshchatx --headless --host 0.0.0.0 --port 8000 --storage-dir %h/meshchatx/storage --reticulum-config-dir %h/.reticulum
Restart=always
RestartSec=3
[Install]
WantedBy=default.target
```
Enable/start user service:
```bash
systemctl --user daemon-reload
systemctl --user enable --now meshchatx.service
systemctl --user status meshchatx.service
```
If you want user services to stay active without login:
```bash
sudo loginctl enable-linger pi
```
### Service management commands
```bash
sudo systemctl restart meshchatx.service
sudo systemctl stop meshchatx.service
sudo systemctl disable meshchatx.service
```
Useful logs and troubleshooting:
```bash
journalctl -u meshchatx.service -f
journalctl -u meshchatx.service -n 200 --no-pager
systemctl show meshchatx.service -p ExecStart -p User -p Group
```
## Notes
- Reticulum configuration and identity data are stored in the service user's home
directory by default (for example `~/.reticulum` and MeshChatX storage paths).
- If you attach RNode hardware by USB, make sure the service user has permission
to access serial devices (`dialout` group on Debian-based systems).
@@ -1,99 +1,232 @@
# MeshChat on a Raspberry Pi
# MeshChatX on Raspberry Pi
A simple guide to install [MeshChat](https://github.com/liamcottle/reticulum-meshchat) on a Raspberry Pi.
This guide shows a simple headless setup for running MeshChatX on a Raspberry Pi 4
with a web UI you can access from another device on your network.
This would allow you to connect an [RNode](https://github.com/markqvist/RNode_Firmware) (such as a Heltec v3) to the Rasbperry Pi via USB, and then access the MeshChat Web UI from another machine on your network.
This install path uses a release wheel, which already includes frontend assets.
My intended use case is to run the Pi + RNode combo from my solar-powered shed, and access the MeshChat Web UI via WiFi.
## Automated Setup Scripts
> Note: This has been tested on a Raspberry Pi 4 Model B
## Install Raspberry Pi OS
If you haven't already done so, the first step is to install Raspberry Pi OS onto an sdcard, and then boot up the Pi. Once booted, follow the below commands.
## Update System
If you want one-command setup, use the interactive installer from repo root:
```bash
bash scripts/rpi/install_meshchatx.sh
```
The installer guides you through:
- Optional `espeak-ng` install (tries apt/dnf/pacman)
- Install method (`pipx` or `venv + pip`)
- Storage and Reticulum directories
- Bind host and port (with availability check)
- HTTPS on/off (default on)
- Service mode (`system`, `user`, or `none`)
- Service startup verification (`Running on ...`)
If startup validation fails, it prints recent logs and stops the service to avoid
restart loops.
The installer also applies compatibility dependencies needed by older wheel
releases.
For all options:
```bash
bash scripts/rpi/install_meshchatx.sh --help
```
## 1) Install Base Dependencies
```bash
sudo apt update
sudo apt upgrade
sudo apt upgrade -y
sudo apt install -y python3 python3-pip pipx
```
## Install System Dependencies
## 2) Enable pipx Path
```
sudo apt install git
sudo apt install python3-pip
```bash
pipx ensurepath
source ~/.profile
```
## Install NodeJS v22
If `pipx` is not available in your distro package repo, install it with:
```
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/nodesource.gpg
NODE_MAJOR=24
echo "deb [signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
sudo apt update
sudo apt install nodejs
```bash
python3 -m pip install --user pipx
python3 -m pipx ensurepath
source ~/.profile
```
## Install pnpm
## 3) Install MeshChatX with pipx (recommended)
```
corepack enable
corepack prepare pnpm@latest --activate
Preferred option (recommended): install from a release wheel (4.4.0 or newer),
because the wheel bundles frontend assets.
```bash
pipx install /path/to/reticulum_meshchatx-<version>-py3-none-any.whl
```
## Install MeshChat
Direct example (v4.4.0):
```
git clone https://github.com/liamcottle/reticulum-meshchat
cd reticulum-meshchat
pip install -r requirements.txt --break-system-packages
pnpm install --prod
pnpm run build-frontend
```bash
pipx install "https://git.quad4.io/RNS-Things/MeshChatX/releases/download/v4.4.0/reticulum_meshchatx-4.4.0-py3-none-any.whl"
```
## Run MeshChat
`py3-none-any` wheels are architecture-independent, so the same wheel artifact
works on Raspberry Pi ARM and x86_64 Linux systems.
```
python meshchat.py --headless --host 0.0.0.0
Upgrade example:
```bash
pipx upgrade meshchatx
```
## Configure Service
## 4) Install MeshChatX without pipx (venv + pip)
Adding a `systemd` service will allow MeshChat to run in the background when you disconnect from the Pi's terminal.
If you prefer not to use pipx:
```
sudo nano /etc/systemd/system/reticulum-meshchat.service
```bash
mkdir -p ~/meshchatx
cd ~/meshchatx
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install "https://git.quad4.io/RNS-Things/MeshChatX/releases/download/v4.4.0/reticulum_meshchatx-4.4.0-py3-none-any.whl"
```
Run command in venv mode:
```bash
~/meshchatx/.venv/bin/meshchatx --headless --host 0.0.0.0 --port 8000
```
## 5) Run MeshChatX (Headless)
```bash
meshchatx --headless --host 0.0.0.0 --port 8000
```
Then open:
```text
http://<pi-ip>:8000
```
## 6) Configure a systemd Service
`systemd` keeps MeshChatX running in the background and starts it automatically
on boot.
You have two service styles:
- System service (`/etc/systemd/system/...`) for always-on host services.
- User service (`~/.config/systemd/user/...`) for per-user sessions.
### Option A: System service (recommended for Pi node/server use)
Create `/etc/systemd/system/meshchatx.service`:
```ini
[Unit]
Description=reticulum-meshchat
After=network.target
StartLimitIntervalSec=0
Description=MeshChatX Headless (system service)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=pi
Group=pi
WorkingDirectory=/home/pi/meshchatx
Environment="PATH=/home/pi/.local/bin:/usr/bin:/bin"
ExecStart=/home/pi/.local/bin/meshchatx --headless --host 0.0.0.0 --port 8000 --storage-dir /home/pi/meshchatx/storage --reticulum-config-dir /home/pi/.reticulum
Restart=always
RestartSec=1
User=liamcottle
Group=liamcottle
WorkingDirectory=/home/liamcottle/reticulum-meshchat
ExecStart=/usr/bin/env python /home/liamcottle/reticulum-meshchat/meshchat.py --headless --host 0.0.0.0
RestartSec=3
[Install]
WantedBy=multi-user.target
```
> Note: Make sure to update the usernames in the service file if needed.
The above service file is for pipx installs. For venv installs, use:
```
sudo systemctl enable reticulum-meshchat.service
sudo systemctl start reticulum-meshchat.service
sudo systemctl status reticulum-meshchat.service
```ini
[Service]
Type=simple
User=pi
Group=pi
WorkingDirectory=/home/pi/meshchatx
Environment="PATH=/home/pi/meshchatx/.venv/bin:/usr/bin:/bin"
ExecStart=/home/pi/meshchatx/.venv/bin/meshchatx --headless --host 0.0.0.0 --port 8000 --storage-dir /home/pi/meshchatx/storage --reticulum-config-dir /home/pi/.reticulum
Restart=always
RestartSec=3
```
You should now be able to access MeshChat via your Pi's IP address.
Update `User`, `Group`, and paths if your install location is different.
> Note: Don't forget to include the default port `8000`
Enable and start:
```bash
mkdir -p /home/pi/meshchatx/storage /home/pi/.reticulum
sudo chown -R pi:pi /home/pi/meshchatx
sudo systemctl daemon-reload
sudo systemctl enable --now meshchatx.service
sudo systemctl status meshchatx.service
```
### Option B: User service (no sudo system unit)
Create `~/.config/systemd/user/meshchatx.service`:
```ini
[Unit]
Description=MeshChatX Headless (user service)
After=network-online.target
[Service]
Type=simple
WorkingDirectory=%h/meshchatx
Environment="PATH=%h/.local/bin:/usr/bin:/bin"
ExecStart=%h/.local/bin/meshchatx --headless --host 0.0.0.0 --port 8000 --storage-dir %h/meshchatx/storage --reticulum-config-dir %h/.reticulum
Restart=always
RestartSec=3
[Install]
WantedBy=default.target
```
Enable/start user service:
```bash
systemctl --user daemon-reload
systemctl --user enable --now meshchatx.service
systemctl --user status meshchatx.service
```
If you want user services to stay active without login:
```bash
sudo loginctl enable-linger pi
```
### Service management commands
```bash
sudo systemctl restart meshchatx.service
sudo systemctl stop meshchatx.service
sudo systemctl disable meshchatx.service
```
Useful logs and troubleshooting:
```bash
journalctl -u meshchatx.service -f
journalctl -u meshchatx.service -n 200 --no-pager
systemctl show meshchatx.service -p ExecStart -p User -p Group
```
## Notes
- Reticulum configuration and identity data are stored in the service user's home
directory by default (for example `~/.reticulum` and MeshChatX storage paths).
- If you attach RNode hardware by USB, make sure the service user has permission
to access serial devices (`dialout` group on Debian-based systems).
+494
View File
@@ -0,0 +1,494 @@
#!/usr/bin/env bash
set -euo pipefail
DEFAULT_WHEEL_URL="https://git.quad4.io/RNS-Things/MeshChatX/releases/download/v4.4.0/reticulum_meshchatx-4.4.0-py3-none-any.whl"
RUN_USER="${SUDO_USER:-$USER}"
RUN_GROUP="$(id -gn "$RUN_USER")"
USER_HOME="$(eval echo "~${RUN_USER}")"
if [[ -t 1 ]] && command -v tput >/dev/null 2>&1; then
C_RESET="$(tput sgr0)"
C_BOLD="$(tput bold)"
C_RED="$(tput setaf 1)"
C_GREEN="$(tput setaf 2)"
C_YELLOW="$(tput setaf 3)"
C_BLUE="$(tput setaf 4)"
else
C_RESET=""
C_BOLD=""
C_RED=""
C_GREEN=""
C_YELLOW=""
C_BLUE=""
fi
note() {
echo "${C_BLUE}${C_BOLD}==>${C_RESET} $*"
}
warn() {
echo "${C_YELLOW}${C_BOLD}WARN:${C_RESET} $*"
}
err() {
echo "${C_RED}${C_BOLD}ERROR:${C_RESET} $*" >&2
}
ok() {
echo "${C_GREEN}${C_BOLD}OK:${C_RESET} $*"
}
run_as_user() {
local cmd="$1"
if [[ "$EUID" -eq 0 && "$RUN_USER" != "root" ]]; then
sudo -u "$RUN_USER" -H bash -lc "$cmd"
else
bash -lc "$cmd"
fi
}
prompt_default() {
local prompt="$1"
local default="$2"
local value=""
read -r -p "$prompt [$default]: " value
if [[ -z "$value" ]]; then
value="$default"
fi
echo "$value"
}
prompt_yes_no() {
local prompt="$1"
local default="${2:-y}"
local answer=""
local hint="Y/n"
if [[ "$default" == "n" ]]; then
hint="y/N"
fi
while true; do
read -r -p "$prompt ($hint): " answer
if [[ -z "$answer" ]]; then
answer="$default"
fi
case "${answer,,}" in
y|yes) return 0 ;;
n|no) return 1 ;;
*)
echo "Please answer y or n."
;;
esac
done
}
pick_package_manager() {
if command -v apt-get >/dev/null 2>&1; then
echo "apt"
return
fi
if command -v dnf >/dev/null 2>&1; then
echo "dnf"
return
fi
if command -v pacman >/dev/null 2>&1; then
echo "pacman"
return
fi
echo "none"
}
install_package_if_possible() {
local package="$1"
local mgr
mgr="$(pick_package_manager)"
case "$mgr" in
apt)
if [[ "$EUID" -eq 0 ]]; then
apt-get update && apt-get install -y "$package"
else
sudo apt-get update && sudo apt-get install -y "$package"
fi
;;
dnf)
if [[ "$EUID" -eq 0 ]]; then
dnf install -y "$package"
else
sudo dnf install -y "$package"
fi
;;
pacman)
if [[ "$EUID" -eq 0 ]]; then
pacman -Sy --noconfirm "$package"
else
sudo pacman -Sy --noconfirm "$package"
fi
;;
none)
warn "No supported package manager found (apt/dnf/pacman). Skipping install for $package."
;;
esac
}
check_port_available() {
local host="$1"
local port="$2"
python3 - "$host" "$port" <<'PY'
import socket
import sys
host = sys.argv[1]
port = int(sys.argv[2])
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.bind((host, port))
except OSError:
sys.exit(1)
finally:
s.close()
sys.exit(0)
PY
}
detect_arch() {
python3 - <<'PY'
import platform
print(platform.machine().strip().lower())
PY
}
is_supported_rpi_arch() {
local arch="$1"
case "$arch" in
armv6l|armv7l|aarch64|arm64)
return 0
;;
*)
return 1
;;
esac
}
write_system_service() {
local exec_cmd="$1"
local workdir="$2"
local path_value="$3"
local svc="/etc/systemd/system/meshchatx.service"
if [[ "$EUID" -eq 0 ]]; then
SUDO=""
else
SUDO="sudo"
fi
$SUDO tee "$svc" >/dev/null <<EOF
[Unit]
Description=MeshChatX Headless (system service)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=${RUN_USER}
Group=${RUN_GROUP}
WorkingDirectory=${workdir}
Environment="PATH=${path_value}"
ExecStart=${exec_cmd}
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
EOF
}
write_user_service() {
local exec_cmd="$1"
local workdir="$2"
local path_value="$3"
local svc_path="${USER_HOME}/.config/systemd/user/meshchatx.service"
run_as_user "mkdir -p '${USER_HOME}/.config/systemd/user'"
run_as_user "cat > '${svc_path}' <<'EOF'
[Unit]
Description=MeshChatX Headless (user service)
After=network-online.target
[Service]
Type=simple
WorkingDirectory=${workdir}
Environment=\"PATH=${path_value}\"
ExecStart=${exec_cmd}
Restart=always
RestartSec=3
[Install]
WantedBy=default.target
EOF"
}
handle_service_start_failure() {
local mode="$1"
local reason="$2"
err "$reason"
warn "Service startup failed. Recent logs:"
if [[ "$mode" == "system" ]]; then
sudo journalctl -u meshchatx.service -n 200 --no-pager || true
sudo systemctl stop meshchatx.service || true
sudo systemctl reset-failed meshchatx.service || true
else
run_as_user "journalctl --user -u meshchatx.service -n 200 --no-pager" || true
run_as_user "systemctl --user stop meshchatx.service || true"
run_as_user "systemctl --user reset-failed meshchatx.service || true"
fi
err "Service was stopped/reset. Fix config and run installer again."
exit 1
}
verify_service_started() {
local mode="$1"
local probe_host="$2"
local probe_port="$3"
local https_enabled="$4"
local tries=40
local log_cmd=""
local stop_cmd=""
local scheme="https"
if [[ "$https_enabled" == "no" ]]; then
scheme="http"
fi
if [[ "$mode" == "system" ]]; then
log_cmd="sudo journalctl -u meshchatx.service -n 200 --no-pager"
stop_cmd="sudo systemctl stop meshchatx.service; sudo systemctl reset-failed meshchatx.service"
else
log_cmd="journalctl --user -u meshchatx.service -n 200 --no-pager"
stop_cmd="systemctl --user stop meshchatx.service || true; systemctl --user reset-failed meshchatx.service || true"
fi
note "Verifying service startup via ${scheme}://${probe_host}:${probe_port}/api/v1/status ..."
for _ in $(seq 1 "$tries"); do
if python3 - "$scheme" "$probe_host" "$probe_port" <<'PY'
import json
import ssl
import sys
import urllib.request
scheme, host, port = sys.argv[1], sys.argv[2], int(sys.argv[3])
url = f"{scheme}://{host}:{port}/api/v1/status"
ctx = ssl._create_unverified_context() if scheme == "https" else None
req = urllib.request.Request(url, method="GET")
try:
with urllib.request.urlopen(req, timeout=2, context=ctx) as resp:
if resp.status != 200:
raise RuntimeError(f"unexpected status {resp.status}")
data = json.loads(resp.read().decode("utf-8"))
if data.get("status") != "ok":
raise RuntimeError("status payload is not ok")
except Exception:
sys.exit(1)
sys.exit(0)
PY
then
ok "Service started successfully (status endpoint is healthy)."
return 0
fi
sleep 1
done
err "Service did not pass status endpoint health check."
warn "Recent logs:"
if [[ "$mode" == "system" ]]; then
sudo journalctl -u meshchatx.service -n 200 --no-pager || true
eval "$stop_cmd"
else
run_as_user "$log_cmd" || true
run_as_user "$stop_cmd"
fi
err "Service was stopped to prevent restart loops."
return 1
}
main() {
note "MeshChatX Raspberry Pi Interactive Installer"
echo "Detected user: ${RUN_USER} (group: ${RUN_GROUP})"
local arch
arch="$(detect_arch)"
echo "Detected architecture: ${arch}"
if is_supported_rpi_arch "$arch"; then
ok "Detected Raspberry Pi ARM architecture (${arch})."
else
warn "Detected non-RPi arch (${arch}). Script can still run, but this guide targets Raspberry Pi ARM."
fi
note "The wheel is py3-none-any, so it is architecture-independent (32-bit and 64-bit ARM are supported)."
echo
if prompt_yes_no "Do you want to install espeak-ng?" "y"; then
note "Installing espeak-ng (best effort)..."
if ! install_package_if_possible "espeak-ng"; then
warn "Could not install espeak-ng automatically; continuing."
fi
else
note "Skipping espeak-ng installation."
fi
local method_choice=""
while [[ "$method_choice" != "1" && "$method_choice" != "2" ]]; do
echo
echo "Choose installation method:"
echo " 1) pipx (recommended)"
echo " 2) venv + pip"
read -r -p "Selection [1/2]: " method_choice
if [[ -z "$method_choice" ]]; then
method_choice="1"
fi
done
local wheel_url
wheel_url="$(prompt_default "Wheel URL" "$DEFAULT_WHEEL_URL")"
local install_root
install_root="$(prompt_default "Install root directory" "${USER_HOME}/meshchatx")"
local storage_dir
storage_dir="$(prompt_default "Storage directory" "${install_root}/storage")"
local rns_dir
rns_dir="$(prompt_default "Reticulum config directory" "${USER_HOME}/.reticulum")"
local bind_host
bind_host="$(prompt_default "Bind IP/host" "0.0.0.0")"
local bind_port
bind_port="$(prompt_default "Bind port" "8000")"
if check_port_available "$bind_host" "$bind_port"; then
ok "Port ${bind_port} is available on ${bind_host}."
else
warn "Port ${bind_port} appears to be in use on ${bind_host}."
if ! prompt_yes_no "Continue anyway?" "n"; then
err "Aborted."
exit 1
fi
fi
local https_enabled="yes"
if ! prompt_yes_no "Enable HTTPS?" "y"; then
https_enabled="no"
fi
local service_mode="none"
if prompt_yes_no "Do you want to configure a systemd service?" "y"; then
service_mode=""
while [[ "$service_mode" != "system" && "$service_mode" != "user" ]]; do
service_mode="$(prompt_default "Service mode (system/user)" "system")"
done
fi
local no_https_flag=""
if [[ "$https_enabled" == "no" ]]; then
no_https_flag=" --no-https"
fi
local probe_host="$bind_host"
if [[ "$bind_host" == "0.0.0.0" ]]; then
probe_host="127.0.0.1"
elif [[ "$bind_host" == "::" ]]; then
probe_host="::1"
fi
note "Preparing directories..."
if [[ "$EUID" -eq 0 ]]; then
install -d -m 755 -o "$RUN_USER" -g "$RUN_GROUP" "$install_root" "$storage_dir" "$rns_dir"
else
mkdir -p "$install_root" "$storage_dir" "$rns_dir"
if command -v sudo >/dev/null 2>&1; then
sudo install -d -m 755 -o "$RUN_USER" -g "$RUN_GROUP" "$install_root" "$storage_dir" "$rns_dir" >/dev/null 2>&1 || true
fi
fi
local bin_path=""
local venv_path="${install_root}/.venv"
if [[ "$method_choice" == "1" ]]; then
if ! command -v pipx >/dev/null 2>&1; then
err "pipx not found. Install pipx first or choose venv method."
exit 1
fi
note "Installing MeshChatX via pipx..."
run_as_user "pipx ensurepath >/dev/null 2>&1 || true"
run_as_user "pipx install --force '${wheel_url}'"
run_as_user "pipx inject reticulum-meshchatx packaging >/dev/null 2>&1 || true"
bin_path="${USER_HOME}/.local/bin/meshchatx"
else
note "Installing MeshChatX via venv + pip..."
run_as_user "python3 -m venv '${venv_path}'"
run_as_user "'${venv_path}/bin/python' -m pip install --upgrade pip"
run_as_user "'${venv_path}/bin/python' -m pip install '${wheel_url}'"
run_as_user "'${venv_path}/bin/python' -m pip install packaging"
bin_path="${venv_path}/bin/meshchatx"
fi
if [[ ! -x "$bin_path" ]]; then
err "meshchatx binary not found at: $bin_path"
exit 1
fi
local exec_cmd="${bin_path} --headless --host ${bind_host} --port ${bind_port} --storage-dir ${storage_dir} --reticulum-config-dir ${rns_dir}${no_https_flag}"
local path_env="${venv_path}/bin:${USER_HOME}/.local/bin:/usr/bin:/bin"
if [[ "$service_mode" == "none" ]]; then
ok "Install complete."
echo
echo "Run command:"
echo " ${exec_cmd}"
exit 0
fi
if [[ "$service_mode" == "system" ]]; then
note "Creating system service..."
write_system_service "$exec_cmd" "$install_root" "$path_env"
if [[ "$EUID" -eq 0 ]]; then
if ! systemctl daemon-reload; then
handle_service_start_failure "system" "systemctl daemon-reload failed."
fi
if ! systemctl enable --now meshchatx.service; then
handle_service_start_failure "system" "systemctl enable/start failed."
fi
else
if ! sudo systemctl daemon-reload; then
handle_service_start_failure "system" "sudo systemctl daemon-reload failed."
fi
if ! sudo systemctl enable --now meshchatx.service; then
handle_service_start_failure "system" "sudo systemctl enable/start failed."
fi
fi
if ! verify_service_started "system" "$probe_host" "$bind_port" "$https_enabled"; then
exit 1
fi
ok "System service is enabled and running."
else
if [[ "$service_mode" == "user" ]]; then
note "Creating user service..."
write_user_service "$exec_cmd" "$install_root" "$path_env"
if ! run_as_user "systemctl --user daemon-reload"; then
handle_service_start_failure "user" "systemctl --user daemon-reload failed."
fi
if ! run_as_user "systemctl --user enable --now meshchatx.service"; then
handle_service_start_failure "user" "systemctl --user enable/start failed."
fi
if ! verify_service_started "user" "$probe_host" "$bind_port" "$https_enabled"; then
exit 1
fi
ok "User service is enabled and running."
fi
fi
echo
echo "Web UI:"
echo " https://${bind_host}:${bind_port}"
if [[ "$https_enabled" == "no" ]]; then
echo " (HTTP mode enabled)"
fi
}
main "$@"