Compare commits

..

17 Commits

Author SHA1 Message Date
Jack Kingsman c7248222dd Updating changelog + build for 3.10.0 2026-04-10 11:16:16 -07:00
Jack Kingsman 1e18a91f12 Merge pull request #172 from YourSandwich/aur-install-instructions
Add Arch Linux (AUR) packaging infrastructure
2026-04-10 10:54:49 -07:00
Jack Kingsman 18db6e4dd8 Make test script executable 2026-04-10 10:49:49 -07:00
Jack Kingsman 2393dadf1b Unload the service on uninstall 2026-04-10 10:48:38 -07:00
Jack Kingsman fd26576e0d Use correct email 2026-04-10 10:47:21 -07:00
Sandwich cb5a76eb5f Replace manual user/group creation with sysusers.d and tmpfiles.d 2026-04-10 19:23:01 +02:00
Jack Kingsman 7f5dde119f Update AGENTS.md 2026-04-10 00:15:57 -07:00
Jack Kingsman 799a721761 Be more defensive about systemd detection 2026-04-10 00:10:53 -07:00
Jack Kingsman 152a584f35 Fix TCP host 2026-04-10 00:10:41 -07:00
Jack Kingsman 5cc0476426 Fix port numbering 2026-04-10 00:06:22 -07:00
Jack Kingsman e468c6c161 Change command palette shortcut 2026-04-09 23:45:16 -07:00
Jack Kingsman e33537018b Fix AUR username 2026-04-09 23:11:02 -07:00
Jack Kingsman 0727793560 Add test script 2026-04-09 23:08:32 -07:00
Jack Kingsman 5c4e04e024 Skip daemon reload if systemctl isn't around 2026-04-09 23:08:26 -07:00
Jack Kingsman 967269ef7d Initial AUR work 2026-04-09 23:08:22 -07:00
Jack Kingsman 1903797d0d Fix broken statistics pane e2e test 2026-04-09 22:30:12 -07:00
Sandwich 424da7e232 Add Arch Linux (AUR) install instructions to README
Adds "Install Path 3: Arch Linux (AUR)" section covering both AUR
helper and manual makepkg installation, linking to the published
remoteterm-meshcore AUR package.

Closes #171
2026-04-09 03:51:39 +02:00
17 changed files with 451 additions and 11 deletions
+73
View File
@@ -0,0 +1,73 @@
name: Publish AUR package
# Pushes the contents of pkg/aur/ to the remoteterm-meshcore AUR repository
# whenever a GitHub release is published. Can also be triggered manually for
# testing or out-of-band republishes.
#
# Required secrets:
# AUR_SSH_PRIVATE_KEY Private SSH key registered with the AUR maintainer
# account that owns the remoteterm-meshcore package.
# AUR_COMMIT_EMAIL Email used for the AUR git commit identity.
on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: 'Version to publish (no v prefix, e.g. 3.9.1)'
required: true
concurrency:
# Serialize publishes so a fast back-to-back release sequence cannot race
# two pushes against the AUR repo. The later one wins by virtue of being
# the final state.
group: publish-aur
cancel-in-progress: false
jobs:
publish-aur:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Resolve version from event
id: version
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION="${{ inputs.version }}"
else
VERSION="${{ github.event.release.tag_name }}"
fi
VERSION="${VERSION#v}"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Publishing AUR package for version $VERSION"
- name: Stamp pkgver into PKGBUILD
run: |
sed -i "s/^pkgver=.*/pkgver=${{ steps.version.outputs.version }}/" pkg/aur/PKGBUILD
sed -i "s/^pkgrel=.*/pkgrel=1/" pkg/aur/PKGBUILD
- name: Publish to AUR
uses: KSXGitHub/github-actions-deploy-aur@v4.1.2
with:
pkgname: remoteterm-meshcore
pkgbuild: pkg/aur/PKGBUILD
assets: |
pkg/aur/remoteterm-meshcore.install
pkg/aur/remoteterm-meshcore.service
pkg/aur/remoteterm-meshcore.sysusers
pkg/aur/remoteterm-meshcore.tmpfiles
pkg/aur/remoteterm.env
commit_username: jackkingsman
commit_email: ${{ secrets.AUR_COMMIT_EMAIL }}
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
commit_message: "Update to ${{ steps.version.outputs.version }}"
# Recompute sha256sums from the live release tarball + the bundled
# service/env files. The committed PKGBUILD has SKIP placeholders.
updpkgsums: true
# Validate the PKGBUILD parses and sources download, but skip the
# actual build (which would run uv sync + npm install for several
# minutes of CI time on every release).
test: true
test_flags: --clean --cleanbuild --nodeps --nobuild
+3 -1
View File
@@ -209,6 +209,7 @@ This message-layer echo/path handling is independent of raw-packet storage dedup
│ │ ├── MapView.tsx # Leaflet map showing node locations
│ │ └── ...
│ └── vite.config.ts
├── pkg/aur/ # AUR package files (PKGBUILD, systemd service, env, install hooks)
├── scripts/ # Quality / release helpers (listing below is representative, not exhaustive)
│ ├── build/
│ │ ├── collect_licenses.sh # Gather third-party license attributions
@@ -216,7 +217,8 @@ This message-layer echo/path handling is independent of raw-packet storage dedup
│ ├── quality/
│ │ ├── all_quality.sh # Repo-standard autofix + validate gate
│ │ ├── e2e.sh # End-to-end test runner
│ │ ── extended_quality.sh # Quality gate plus e2e and Docker matrix
│ │ ── extended_quality.sh # Quality gate plus e2e and Docker matrix
│ │ └── test_aur_package.sh # Build + install AUR package in Arch Docker containers
│ └── setup/
│ ├── fetch_prebuilt_frontend.py # Download release frontend fallback
│ └── install_service.sh # Install/configure Linux systemd service
+9
View File
@@ -1,3 +1,12 @@
## [3.10.0] - 2026-04-10
* Feature: Add Arch AUR package
* Feature: 72hr packet density view in statistics
* Feature: Add warnings for event loop selection for MQTT on Windows startup
* Bufix: Bump Apprise to 1.9.9 to fix Matrix bug
* Misc: More memory-conscious on recent contact fetch
* Misc: Fix statistics pane e2e test
## [3.9.0] - 2026-04-06
* Feature: Add hop counts to hop-width selection options
+2 -2
View File
@@ -56,7 +56,7 @@ SOFTWARE.
</details>
### apprise (1.9.7) — BSD-2-Clause
### apprise (1.9.9) — BSD-2-Clause
<details>
<summary>Full license text</summary>
@@ -64,7 +64,7 @@ SOFTWARE.
```
BSD 2-Clause License
Copyright (c) 2025, Chris Caron <lead2gold@gmail.com>
Copyright (c) 2026, Chris Caron <lead2gold@gmail.com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
+23
View File
@@ -161,6 +161,29 @@ To stop:
sudo docker compose down
```
## Install Path 3: Arch Linux (AUR)
A [`remoteterm-meshcore`](https://aur.archlinux.org/packages/remoteterm-meshcore) package is available in the AUR. Install it with an AUR helper or build it manually:
```bash
# with an AUR helper
yay -S remoteterm-meshcore
# or manually
git clone https://aur.archlinux.org/remoteterm-meshcore.git
cd remoteterm-meshcore
makepkg -si
```
Configure your radio connection, then start the service:
```bash
sudo vi /etc/remoteterm-meshcore/remoteterm.env
sudo systemctl enable --now remoteterm-meshcore
```
Access the app at http://localhost:8000.
## Standard Environment Variables
Only one transport may be active at a time. If multiple are set, the server will refuse to start.
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "remoteterm-meshcore-frontend",
"private": true,
"version": "3.9.0",
"version": "3.10.0",
"type": "module",
"scripts": {
"dev": "vite",
+4 -4
View File
@@ -7,7 +7,7 @@ import {
Radio,
Route,
Search,
Sparkles,
Star,
User,
Waypoints,
} from 'lucide-react';
@@ -296,7 +296,7 @@ export function CommandPalette({
>
<Hash className="text-muted-foreground" />
<span>{ch.name}</span>
<Sparkles className="ml-auto h-3 w-3 text-yellow-500" />
<Star className="ml-auto h-3 w-3 text-favorite" />
</CommandItem>
))}
</CommandGroup>
@@ -384,7 +384,7 @@ function ContactGroup({
>
<Icon className="text-muted-foreground" />
<span>{displayName}</span>
{showStar && <Sparkles className="ml-auto h-3 w-3 text-yellow-500" />}
{showStar && <Star className="ml-auto h-3 w-3 text-favorite" />}
</CommandItem>
))}
</CommandGroup>
@@ -419,7 +419,7 @@ function RepeaterGroup({
>
<Waypoints className="text-muted-foreground" />
<span>{displayName}</span>
{showStar && <Sparkles className="ml-auto h-3 w-3 text-yellow-500" />}
{showStar && <Star className="ml-auto h-3 w-3 text-favorite" />}
</CommandItem>,
<CommandItem
key={`${c.public_key}-acl`}
+127
View File
@@ -0,0 +1,127 @@
# Maintainer: Jack Kingsman <jack@jackkingsman.me>
pkgname=remoteterm-meshcore
# pkgver is rewritten by .github/workflows/publish-aur.yml on each release.
pkgver=3.9.0
pkgrel=1
pkgdesc='Web interface for MeshCore mesh radio networks'
arch=(x86_64 aarch64)
url='https://github.com/jkingsman/Remote-Terminal-for-MeshCore'
license=('MIT')
# No system python dependency — we bundle a standalone interpreter via
# python-build-standalone so the package is immune to Arch python ABI bumps.
depends=(glibc)
makedepends=(uv nodejs npm)
optdepends=('bluez: BLE transport support')
backup=(etc/remoteterm-meshcore/remoteterm.env)
# The bundled python-build-standalone binary ships pre-stripped. makepkg's
# default strip pass corrupts its unusual ELF layout (.dynstr not in segment),
# so we disable stripping for the whole package.
options=(!strip)
install=remoteterm-meshcore.install
source=(
"$pkgname-$pkgver.tar.gz::https://github.com/jkingsman/Remote-Terminal-for-MeshCore/archive/refs/tags/$pkgver.tar.gz"
"remoteterm-meshcore.service"
"remoteterm.env"
"remoteterm-meshcore.sysusers"
"remoteterm-meshcore.tmpfiles"
)
# sha256sums are recomputed by `updpkgsums` in the publish workflow before
# the PKGBUILD is pushed to AUR. The committed values are intentionally SKIP
# so the file is honest about not tracking real hashes in this repo.
sha256sums=('SKIP'
'SKIP'
'SKIP'
'SKIP'
'SKIP')
# python-build-standalone: stripped install_only builds (~30 MB each).
# Bump _pyver and _pybuilddate when updating the bundled interpreter.
_pyver=3.13.13
_pybuilddate=20260408
source_x86_64=("python-${_pyver}-x86_64.tar.gz::https://github.com/astral-sh/python-build-standalone/releases/download/${_pybuilddate}/cpython-${_pyver}+${_pybuilddate}-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz")
sha256sums_x86_64=('SKIP')
source_aarch64=("python-${_pyver}-aarch64.tar.gz::https://github.com/astral-sh/python-build-standalone/releases/download/${_pybuilddate}/cpython-${_pyver}+${_pybuilddate}-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz")
sha256sums_aarch64=('SKIP')
_srcname="Remote-Terminal-for-MeshCore-$pkgver"
build() {
cd "$_srcname"
# Build frontend
cd frontend
npm ci
npm run build
cd ..
# Create venv using the bundled standalone Python interpreter, then install
# Python dependencies into it. This produces a fully self-contained venv
# that does not reference the system Python at all.
uv venv --python "$srcdir/python/bin/python3" .venv
uv sync --no-dev --frozen
}
package() {
cd "$_srcname"
local _optdir=/opt/remoteterm-meshcore
local _instdir="$pkgdir$_optdir"
# App source
install -d "$_instdir"
cp -r app "$_instdir/"
cp pyproject.toml uv.lock "$_instdir/"
# Frontend build
install -d "$_instdir/frontend"
cp -r frontend/dist "$_instdir/frontend/"
# Bundled Python interpreter
cp -a "$srcdir/python" "$_instdir/python"
# Python venv
cp -a .venv "$_instdir/"
# Fix shebangs and venv config: replace build-time paths with final
# install paths so the venv works from /opt after installation.
# sed only operates on regular file contents, so symlinks need separate
# fixup below.
find "$_instdir/.venv/bin" -type f -exec \
sed -i "s|$srcdir/$_srcname/.venv|$_optdir/.venv|g" {} +
find "$_instdir/.venv/bin" -type f -exec \
sed -i "s|$srcdir/python|$_optdir/python|g" {} +
sed -i \
-e "s|$srcdir/$_srcname/.venv|$_optdir/.venv|g" \
-e "s|$srcdir/python|$_optdir/python|g" \
"$_instdir/.venv/pyvenv.cfg" 2>/dev/null || true
# Recreate the venv interpreter symlinks — these are symlinks (not files),
# so sed cannot fix them. Point them at the bundled Python.
ln -sf "$_optdir/python/bin/python3" "$_instdir/.venv/bin/python"
ln -sf python "$_instdir/.venv/bin/python3"
ln -sf python "$_instdir/.venv/bin/python3.13"
# Data directory symlink
ln -s /var/lib/remoteterm-meshcore "$_instdir/data"
# Systemd service
install -Dm644 "$srcdir/remoteterm-meshcore.service" \
"$pkgdir/usr/lib/systemd/system/remoteterm-meshcore.service"
# Environment file
install -Dm640 "$srcdir/remoteterm.env" \
"$pkgdir/etc/remoteterm-meshcore/remoteterm.env"
# System user and data directory
install -Dm644 "$srcdir/remoteterm-meshcore.sysusers" \
"$pkgdir/usr/lib/sysusers.d/remoteterm-meshcore.conf"
install -Dm644 "$srcdir/remoteterm-meshcore.tmpfiles" \
"$pkgdir/usr/lib/tmpfiles.d/remoteterm-meshcore.conf"
# License
install -Dm644 LICENSE.md \
"$pkgdir/usr/share/licenses/$pkgname/LICENSE"
}
+35
View File
@@ -0,0 +1,35 @@
post_install() {
echo "==> Set your radio connection (serial, TCP, or BLE) in"
echo "==> /etc/remoteterm-meshcore/remoteterm.env"
echo "==> Start the service with: systemctl enable --now remoteterm-meshcore"
echo "==> The web UI will be at http://localhost:8000"
}
post_upgrade() {
# Clean orphaned __pycache__ dirs left by the previous Python version
find /opt/remoteterm-meshcore -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
# Skip systemd operations in chroots/containers where the binary may exist
# but PID 1 is not systemd.
if [ -d /run/systemd/system ] && command -v systemctl &>/dev/null; then
systemctl daemon-reload || true
if systemctl is-active --quiet remoteterm-meshcore; then
systemctl restart remoteterm-meshcore || true
fi
fi
}
pre_remove() {
if [ -d /run/systemd/system ] && command -v systemctl &>/dev/null; then
systemctl disable --now remoteterm-meshcore 2>/dev/null || true
fi
}
post_remove() {
if [ -d /run/systemd/system ] && command -v systemctl &>/dev/null; then
systemctl daemon-reload
fi
echo "==> Database and config remain in /var/lib/remoteterm-meshcore/, remoteterm user retained."
echo "==> To fully clean up: sudo rm -rf /var/lib/remoteterm-meshcore"
}
+29
View File
@@ -0,0 +1,29 @@
[Unit]
Description=RemoteTerm for MeshCore
Documentation=https://github.com/jkingsman/Remote-Terminal-for-MeshCore
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=remoteterm
Group=remoteterm
WorkingDirectory=/opt/remoteterm-meshcore
EnvironmentFile=/etc/remoteterm-meshcore/remoteterm.env
ExecStart=/opt/remoteterm-meshcore/.venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000
Restart=on-failure
RestartSec=5s
StateDirectory=remoteterm-meshcore
# Hardening
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
NoNewPrivileges=yes
# Serial port access (uucp group on Arch)
SupplementaryGroups=uucp
[Install]
WantedBy=multi-user.target
+1
View File
@@ -0,0 +1 @@
u remoteterm - "RemoteTerm for MeshCore" /var/lib/remoteterm-meshcore
+1
View File
@@ -0,0 +1 @@
d /var/lib/remoteterm-meshcore 0750 remoteterm remoteterm
+28
View File
@@ -0,0 +1,28 @@
# RemoteTerm for MeshCore configuration
# https://github.com/jkingsman/Remote-Terminal-for-MeshCore
# Transport: uncomment ONE section below
# Serial auto-detect (default — no config needed)
# Serial manual port
#MESHCORE_SERIAL_PORT=/dev/ttyUSB0
# TCP
#MESHCORE_TCP_HOST=192.168.1.100
#MESHCORE_TCP_PORT=5000
# BLE (also requires the optional `bluez` package)
#MESHCORE_BLE_ADDRESS=AA:BB:CC:DD:EE:FF
#MESHCORE_BLE_PIN=123456
# Database
MESHCORE_DATABASE_PATH=/var/lib/remoteterm-meshcore/meshcore.db
# Bots can run arbitrary Python on the server. Leave this set to 'true' unless
# you trust everyone on your network.
MESHCORE_DISABLE_BOTS=true
# HTTP Basic Auth (recommended when bots are enabled)
#MESHCORE_BASIC_AUTH_USERNAME=
#MESHCORE_BASIC_AUTH_PASSWORD=
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "remoteterm-meshcore"
version = "3.9.0"
version = "3.10.0"
description = "RemoteTerm - Web interface for MeshCore radio mesh networks"
readme = "README.md"
requires-python = ">=3.11"
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env bash
set -euo pipefail
# test_aur_package.sh — Build the AUR package in one Arch container, then
# install and run it in a clean Arch container with port 8000 exposed.
#
# Usage:
# ./scripts/quality/test_aur_package.sh [--port PORT]
#
# The script streams application logs until you Ctrl-C.
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
PORT=8000
if [ "${1:-}" = "--port" ]; then PORT="${2:-8000}"; fi
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
ARTIFACT_DIR="$(mktemp -d)"
INSTALL_CONTAINER="remoteterm-aur-test-$$"
cleanup() {
echo
echo -e "${YELLOW}Cleaning up...${NC}"
docker rm -f "$INSTALL_CONTAINER" 2>/dev/null || true
rm -rf "$ARTIFACT_DIR"
echo -e "${GREEN}Done.${NC}"
}
trap cleanup EXIT
# ── Phase 1: Build ────────────────────────────────────────────────────────────
echo -e "${BOLD}=== Phase 1: Build AUR package ===${NC}"
docker run --rm \
-v "$REPO_ROOT/pkg/aur:/pkg:ro" \
-v "$ARTIFACT_DIR:/out" \
archlinux:latest bash -c '
set -euo pipefail
pacman -Syu --noconfirm base-devel git curl >/dev/null 2>&1
curl -LsSf https://astral.sh/uv/install.sh | sh >/dev/null 2>&1
export PATH="$HOME/.local/bin:$PATH"
pacman -S --noconfirm nodejs npm >/dev/null 2>&1
useradd -m builder
echo "builder ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
BUILD_DIR=/home/builder/build
mkdir -p "$BUILD_DIR"
cp /pkg/PKGBUILD /pkg/remoteterm-meshcore.install \
/pkg/remoteterm-meshcore.service /pkg/remoteterm-meshcore.sysusers \
/pkg/remoteterm-meshcore.tmpfiles /pkg/remoteterm.env "$BUILD_DIR/"
chown -R builder:builder "$BUILD_DIR"
echo "Building package..."
su builder -c "export PATH=\"$HOME/.local/bin:\$PATH\" && cd $BUILD_DIR && makepkg -sf --noconfirm" 2>&1
cp "$BUILD_DIR"/remoteterm-meshcore-*.pkg.tar.zst /out/
echo "Package artifact copied to /out/"
ls -lh /out/*.pkg.tar.zst
'
PKG_FILE="$(ls "$ARTIFACT_DIR"/*.pkg.tar.zst 2>/dev/null | head -1)"
if [ -z "$PKG_FILE" ]; then
echo -e "${RED}Build failed — no .pkg.tar.zst produced${NC}"
exit 1
fi
echo -e "${GREEN}Built: $(basename "$PKG_FILE") ($(du -h "$PKG_FILE" | cut -f1))${NC}"
echo
# ── Phase 2: Install and run ─────────────────────────────────────────────────
echo -e "${BOLD}=== Phase 2: Install and run ===${NC}"
docker run -d \
--name "$INSTALL_CONTAINER" \
-p "$PORT:8000" \
-v "$ARTIFACT_DIR:/pkg:ro" \
archlinux:latest bash -c '
set -euo pipefail
# Install the package (sysusers.d creates the remoteterm user, tmpfiles.d creates the data dir)
pacman -Syu --noconfirm >/dev/null 2>&1
pacman -U --noconfirm /pkg/*.pkg.tar.zst
# In a container there is no systemd to trigger sysusers/tmpfiles automatically,
# so run them manually.
systemd-sysusers
systemd-tmpfiles --create
echo "============================================"
echo " RemoteTerm installed — starting server"
echo "============================================"
# Run as the remoteterm service user, matching the systemd unit
exec su -s /bin/bash remoteterm -c "cd /opt/remoteterm-meshcore && exec .venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000"
' >/dev/null
echo -e "${CYAN}Container:${NC} $INSTALL_CONTAINER"
echo -e "${CYAN}Listening:${NC} http://localhost:$PORT"
echo -e "${CYAN}Health: ${NC} http://localhost:$PORT/api/health"
echo
echo -e "${YELLOW}Streaming logs (Ctrl-C to stop and clean up)...${NC}"
echo
docker logs -f "$INSTALL_CONTAINER"
+1 -1
View File
@@ -15,6 +15,6 @@ test.describe('Statistics page', () => {
await expect(page.locator('h4').getByText('Network')).toBeVisible({ timeout: 10_000 });
await expect(page.getByText('Contacts', { exact: true }).first()).toBeVisible();
await expect(page.getByText('Channels', { exact: true }).first()).toBeVisible();
await expect(page.locator('h4').getByText('Packets')).toBeVisible();
await expect(page.locator('h4').getByText('Packets', { exact: true })).toBeVisible();
});
});
Generated
+1 -1
View File
@@ -983,7 +983,7 @@ wheels = [
[[package]]
name = "remoteterm-meshcore"
version = "3.9.0"
version = "3.10.0"
source = { virtual = "." }
dependencies = [
{ name = "aiomqtt" },