mirror of
https://git.quad4.io/RNS-Things/MeshChatX.git
synced 2026-06-04 10:31:21 +00:00
feat(docs): update Raspberry Pi installation guide for MeshChatX with automated setup scripts and detailed service configuration instructions
This commit is contained in:
@@ -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).
|
||||
|
||||
Executable
+494
@@ -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 "$@"
|
||||
Reference in New Issue
Block a user