diff --git a/.github/workflows/publish-aur.yml b/.github/workflows/publish-aur.yml new file mode 100644 index 0000000..7d0b792 --- /dev/null +++ b/.github/workflows/publish-aur.yml @@ -0,0 +1,71 @@ +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.env + commit_username: jkingsman + 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 diff --git a/pkg/aur/PKGBUILD b/pkg/aur/PKGBUILD new file mode 100644 index 0000000..2dd3eb6 --- /dev/null +++ b/pkg/aur/PKGBUILD @@ -0,0 +1,117 @@ +# Maintainer: Jack Kingsman + +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" +) +# 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') + +# 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" + + # License + install -Dm644 LICENSE.md \ + "$pkgdir/usr/share/licenses/$pkgname/LICENSE" +} diff --git a/pkg/aur/remoteterm-meshcore.install b/pkg/aur/remoteterm-meshcore.install new file mode 100644 index 0000000..a057022 --- /dev/null +++ b/pkg/aur/remoteterm-meshcore.install @@ -0,0 +1,27 @@ +pre_install() { + getent group remoteterm > /dev/null || groupadd -r remoteterm + getent passwd remoteterm > /dev/null || \ + useradd -r -g remoteterm -d /var/lib/remoteterm-meshcore -s /sbin/nologin \ + -c "RemoteTerm for MeshCore" remoteterm +} + +pre_upgrade() { + pre_install +} + +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 + + systemctl daemon-reload + if systemctl is-active --quiet remoteterm-meshcore; then + systemctl restart remoteterm-meshcore + fi +} diff --git a/pkg/aur/remoteterm-meshcore.service b/pkg/aur/remoteterm-meshcore.service new file mode 100644 index 0000000..49e6861 --- /dev/null +++ b/pkg/aur/remoteterm-meshcore.service @@ -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 diff --git a/pkg/aur/remoteterm.env b/pkg/aur/remoteterm.env new file mode 100644 index 0000000..2c0efab --- /dev/null +++ b/pkg/aur/remoteterm.env @@ -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=4000 + +# 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=