diff --git a/.github/workflows/probe-build.yml b/.github/workflows/probe-build.yml index 6fc7581..6df3556 100644 --- a/.github/workflows/probe-build.yml +++ b/.github/workflows/probe-build.yml @@ -11,6 +11,7 @@ env: CARGO_TERM_COLOR: always REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }}-probe + OPENWRT_ARM64_ARCHES: aarch64_generic aarch64_cortex-a53 aarch64_cortex-a72 jobs: linux-amd64: @@ -51,7 +52,7 @@ jobs: compression-level: 0 linux-arm64: - name: Linux arm64 binary and .deb + name: Linux arm64 binary, .deb, and OpenWrt packages runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -73,6 +74,30 @@ jobs: - name: Build Debian package run: cargo deb --package probe --target aarch64-unknown-linux-musl --no-strip --no-build -- --bin cheburprobe + - name: Build legacy OpenWrt opkg packages + run: | + chmod +x probe/openwrt/build-ipk.sh + for arch in $OPENWRT_ARM64_ARCHES; do + OPENWRT_ARCH="$arch" probe/openwrt/build-ipk.sh \ + target/aarch64-unknown-linux-musl/release/cheburprobe \ + target/aarch64-unknown-linux-musl/openwrt + done + + - name: Build current OpenWrt APK packages + run: | + chmod +x probe/openwrt/build-apk.sh + for arch in $OPENWRT_ARM64_ARCHES; do + docker run --rm \ + --env OPENWRT_ARCH="$arch" \ + --env PACKAGE_FILE_ARCH_SUFFIX=1 \ + --volume "$PWD:/work" \ + --workdir /work \ + alpine:edge \ + probe/openwrt/build-apk.sh \ + target/aarch64-unknown-linux-musl/release/cheburprobe \ + target/aarch64-unknown-linux-musl/openwrt + done + - name: Upload Linux arm64 binary uses: actions/upload-artifact@v7 with: @@ -87,6 +112,15 @@ jobs: path: target/aarch64-unknown-linux-musl/debian/*.deb compression-level: 0 + - name: Upload OpenWrt arm64 and LuCI packages + uses: actions/upload-artifact@v7 + with: + name: cheburprobe-openwrt-arm64-packages + path: | + target/aarch64-unknown-linux-musl/openwrt/*.apk + target/aarch64-unknown-linux-musl/openwrt/*.ipk + compression-level: 0 + windows-amd64: name: Windows amd64 binary runs-on: windows-latest diff --git a/probe/README.md b/probe/README.md index e1b7579..34b65e4 100644 --- a/probe/README.md +++ b/probe/README.md @@ -9,7 +9,7 @@ ## Сборка -Готовые бинарные файлы и Debian-пакеты можно скачать на [странице релизов](https://github.com/LowderPlay/cheburcheck/releases). +Готовые бинарные файлы, Debian-пакеты и OpenWrt пакеты для arm64 можно скачать на [странице релизов](https://github.com/LowderPlay/cheburcheck/releases). На Debian-based дистрибутивах можно собрать пакет через `cargo-deb`: @@ -27,6 +27,50 @@ cargo run --package probe --bin cheburprobe -- \ Также доступен Docker-образ, который собирается из `probe/Dockerfile`. +Для OpenWrt на arm64 установите основной `.apk` и пакет LuCI. Пакеты из GitHub +Actions не подписаны, поэтому для локальной установки нужен флаг `--allow-untrusted`: + +Узнать архитектуру на OpenWrt с `apk`: + +```shell +apk --print-arch +``` + +Например, для результата `aarch64_cortex-a53` нужен файл с +`_aarch64_cortex-a53.apk` в имени: + +```shell +apk --allow-untrusted add \ + ./cheburprobe-*_"$(apk --print-arch)".apk \ + ./luci-app-cheburprobe-*.apk +``` + +После установки откройте **Службы → Cheburprobe** в LuCI, заполните ID и токен, +включите сервис и нажмите **Сохранить и применить**. Те же настройки доступны +через UCI в `/etc/config/cheburprobe`. Конфигурационный файл устанавливается с +правами `0600`, поскольку токен хранится в нем в открытом виде. + +На OpenWrt с пакетным менеджером `opkg` установите совместимые `.ipk` пакеты: + +```shell +opkg print-architecture +``` + +Команда может вывести несколько строк. Выберите специфичную для процессора +архитектуру, например `aarch64_cortex-a53`, а не универсальную `all`. + +```shell +opkg install \ + ./cheburprobe_*_<АРХИТЕКТУРА_УСТРОЙСТВА>.ipk \ + ./luci-app-cheburprobe_*_all.ipk +``` + +GitHub Actions выпускает пакеты для `aarch64_generic`, `aarch64_cortex-a53` и +`aarch64_cortex-a72`. Для другого имени архитектуры OpenWrt пакет можно собрать +с переменной `OPENWRT_ARCH`, например +`OPENWRT_ARCH=aarch64_cortex-a53 probe/openwrt/build-apk.sh `. +Та же переменная поддерживается скриптом `build-ipk.sh`. + ## Получение доступа Для подключения сканера нужен `PROBE_ID` и `PROBE_TOKEN`. diff --git a/probe/openwrt/build-apk.sh b/probe/openwrt/build-apk.sh new file mode 100644 index 0000000..cd83e2d --- /dev/null +++ b/probe/openwrt/build-apk.sh @@ -0,0 +1,101 @@ +#!/bin/sh +set -eu + +if [ "$#" -ne 2 ]; then + echo "usage: $0 " >&2 + exit 2 +fi + +BINARY=$1 +OUTPUT_DIR=$2 +APK=${APK:-apk} +ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd) +WORK_DIR=$(mktemp -d) +trap 'rm -rf "$WORK_DIR"' EXIT INT TERM + +mkdir -p "$OUTPUT_DIR" +OUTPUT_DIR=$(CDPATH= cd -- "$OUTPUT_DIR" && pwd) +VERSION=$(sed -n '/^name = "probe"$/,/^\[/{s/^version = "\([^"]*\)"$/\1/p;}' "$ROOT_DIR/probe/Cargo.toml") +ARCH=${OPENWRT_ARCH:-aarch64_generic} +PACKAGE_VERSION="${VERSION}-r1" + +test -n "$VERSION" +test -f "$BINARY" +"$APK" --version | grep -q '^apk-tools 3\.' || { + echo "apk-tools 3.x is required to build OpenWrt APK packages" >&2 + exit 1 +} + +add_openwrt_metadata() { + package_name=$1 + package_root=$2 + conffile=${3:-} + metadata_dir="$package_root/lib/apk/packages" + + mkdir -p "$metadata_dir" + (cd "$package_root" && find . \( -type f -o -type l \) | sed 's|^\./|/|' | sort) \ + > "$metadata_dir/$package_name.list" + + if [ -n "$conffile" ]; then + printf '%s\n' "$conffile" > "$metadata_dir/$package_name.conffiles" + checksum=$(sha256sum "$package_root$conffile" | cut -d ' ' -f1) + printf '%s %s\n' "$conffile" "$checksum" \ + > "$metadata_dir/$package_name.conffiles_static" + fi +} + +build_apk() { + package_name=$1 + package_arch=$2 + package_root=$3 + description=$4 + depends=$5 + post_install=${6:-} + filename_arch= + if [ "${PACKAGE_FILE_ARCH_SUFFIX:-0}" = 1 ] && [ "$package_arch" != noarch ]; then + filename_arch="_${package_arch}" + fi + output="$OUTPUT_DIR/${package_name}-${PACKAGE_VERSION}${filename_arch}.apk" + + set -- \ + mkpkg \ + --info "name:$package_name" \ + --info "version:$PACKAGE_VERSION" \ + --info "tags:openwrt:section=net" \ + --info "description:$description" \ + --info "arch:$package_arch" \ + --info "license:BSD-3-Clause" \ + --info "origin:cheburcheck/probe" \ + --info "url:https://github.com/LowderPlay/cheburcheck" \ + --info "maintainer:Lowder " \ + --info "depends:$depends" + if [ -n "$post_install" ]; then + set -- "$@" --script "post-install:$post_install" + fi + SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH:-0} "$APK" "$@" \ + --no-xattrs --files "$package_root" --output "$output" + echo "$output" +} + +PROBE_ROOT="$WORK_DIR/cheburprobe" +mkdir -p "$PROBE_ROOT/usr/bin" "$PROBE_ROOT/etc/init.d" "$PROBE_ROOT/etc/config" +install -m 0755 "$BINARY" "$PROBE_ROOT/usr/bin/cheburprobe" +install -m 0755 "$ROOT_DIR/probe/openwrt/cheburprobe.init" "$PROBE_ROOT/etc/init.d/cheburprobe" +install -m 0600 "$ROOT_DIR/probe/openwrt/cheburprobe.config" "$PROBE_ROOT/etc/config/cheburprobe" +add_openwrt_metadata cheburprobe "$PROBE_ROOT" /etc/config/cheburprobe +build_apk cheburprobe "$ARCH" "$PROBE_ROOT" \ + "Dynamic network probe daemon for Cheburcheck" "" \ + "$ROOT_DIR/probe/openwrt/cheburprobe.postinst" + +LUCI_ROOT="$WORK_DIR/luci-app-cheburprobe" +mkdir -p "$LUCI_ROOT/usr/share/luci/menu.d" "$LUCI_ROOT/usr/share/rpcd/acl.d" \ + "$LUCI_ROOT/www/luci-static/resources/view/cheburprobe" +install -m 0644 "$ROOT_DIR/probe/openwrt/luci/menu.json" \ + "$LUCI_ROOT/usr/share/luci/menu.d/luci-app-cheburprobe.json" +install -m 0644 "$ROOT_DIR/probe/openwrt/luci/acl.json" \ + "$LUCI_ROOT/usr/share/rpcd/acl.d/luci-app-cheburprobe.json" +install -m 0644 "$ROOT_DIR/probe/openwrt/luci/config.js" \ + "$LUCI_ROOT/www/luci-static/resources/view/cheburprobe/config.js" +add_openwrt_metadata luci-app-cheburprobe "$LUCI_ROOT" +build_apk luci-app-cheburprobe noarch "$LUCI_ROOT" \ + "LuCI configuration interface for Cheburprobe" "cheburprobe luci-base" diff --git a/probe/openwrt/build-ipk.sh b/probe/openwrt/build-ipk.sh new file mode 100644 index 0000000..d9f16c5 --- /dev/null +++ b/probe/openwrt/build-ipk.sh @@ -0,0 +1,71 @@ +#!/bin/sh +set -eu + +if [ "$#" -ne 2 ]; then + echo "usage: $0 " >&2 + exit 2 +fi + +BINARY=$1 +OUTPUT_DIR=$2 +ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd) +WORK_DIR=$(mktemp -d) +trap 'rm -rf "$WORK_DIR"' EXIT INT TERM + +mkdir -p "$OUTPUT_DIR" +OUTPUT_DIR=$(CDPATH= cd -- "$OUTPUT_DIR" && pwd) +VERSION=$(sed -n '/^name = "probe"$/,/^\[/{s/^version = "\([^"]*\)"$/\1/p;}' "$ROOT_DIR/probe/Cargo.toml") +ARCH=${OPENWRT_ARCH:-aarch64_generic} + +test -n "$VERSION" +test -f "$BINARY" + +build_ipk() { + package_name=$1 + package_arch=$2 + package_root=$3 + control_extra=$4 + archive_name="${package_name}_${VERSION}-1_${package_arch}.ipk" + size_kib=$(du -sk "$package_root/data" | cut -f1) + + cat > "$package_root/control/control" < +License: BSD-3-Clause +Section: net +Priority: optional +Installed-Size: ${size_kib} +${control_extra} +EOF + printf '2.0\n' > "$package_root/debian-binary" + (cd "$package_root/control" && tar --sort=name --owner=0 --group=0 --numeric-owner -czf ../control.tar.gz .) + (cd "$package_root/data" && tar --sort=name --owner=0 --group=0 --numeric-owner -czf ../data.tar.gz .) + (cd "$package_root" && ar r "$OUTPUT_DIR/$archive_name" debian-binary control.tar.gz data.tar.gz) + echo "$OUTPUT_DIR/$archive_name" +} + +PROBE_ROOT="$WORK_DIR/cheburprobe" +mkdir -p "$PROBE_ROOT/control" "$PROBE_ROOT/data/usr/bin" \ + "$PROBE_ROOT/data/etc/init.d" "$PROBE_ROOT/data/etc/config" +install -m 0755 "$BINARY" "$PROBE_ROOT/data/usr/bin/cheburprobe" +install -m 0755 "$ROOT_DIR/probe/openwrt/cheburprobe.init" "$PROBE_ROOT/data/etc/init.d/cheburprobe" +install -m 0600 "$ROOT_DIR/probe/openwrt/cheburprobe.config" "$PROBE_ROOT/data/etc/config/cheburprobe" +install -m 0755 "$ROOT_DIR/probe/openwrt/cheburprobe.postinst" "$PROBE_ROOT/control/postinst" +printf '/etc/config/cheburprobe\n' > "$PROBE_ROOT/control/conffiles" +build_ipk cheburprobe "$ARCH" "$PROBE_ROOT" "Description: Dynamic network probe daemon for Cheburcheck + Connects to Cheburcheck and performs network measurements from OpenWrt." + +LUCI_ROOT="$WORK_DIR/luci-app-cheburprobe" +mkdir -p "$LUCI_ROOT/control" "$LUCI_ROOT/data/usr/share/luci/menu.d" \ + "$LUCI_ROOT/data/usr/share/rpcd/acl.d" \ + "$LUCI_ROOT/data/www/luci-static/resources/view/cheburprobe" +install -m 0644 "$ROOT_DIR/probe/openwrt/luci/menu.json" \ + "$LUCI_ROOT/data/usr/share/luci/menu.d/luci-app-cheburprobe.json" +install -m 0644 "$ROOT_DIR/probe/openwrt/luci/acl.json" \ + "$LUCI_ROOT/data/usr/share/rpcd/acl.d/luci-app-cheburprobe.json" +install -m 0644 "$ROOT_DIR/probe/openwrt/luci/config.js" \ + "$LUCI_ROOT/data/www/luci-static/resources/view/cheburprobe/config.js" +build_ipk luci-app-cheburprobe all "$LUCI_ROOT" "Depends: cheburprobe, luci-base +Description: LuCI configuration interface for Cheburprobe" diff --git a/probe/openwrt/cheburprobe.config b/probe/openwrt/cheburprobe.config new file mode 100644 index 0000000..44c92eb --- /dev/null +++ b/probe/openwrt/cheburprobe.config @@ -0,0 +1,13 @@ +config cheburprobe 'main' + option enabled '0' + option probe_id '' + option probe_token '' + option mqtt_host 'wss://cheburcheck.ru/mqtt' + option mqtt_port '443' + option connection_timeout '30' + option max_concurrent_tasks '8' + option traceroute_max_hops '5' + option traceroute_retries '3' + option traceroute_control_hosts '3' + option log_level 'info' + diff --git a/probe/openwrt/cheburprobe.init b/probe/openwrt/cheburprobe.init new file mode 100644 index 0000000..9b6c099 --- /dev/null +++ b/probe/openwrt/cheburprobe.init @@ -0,0 +1,49 @@ +#!/bin/sh /etc/rc.common + +START=95 +STOP=10 +USE_PROCD=1 + +start_service() { + config_load cheburprobe + config_get_bool enabled main enabled 0 + [ "$enabled" -eq 1 ] || return 0 + + config_get probe_id main probe_id + config_get probe_token main probe_token + config_get mqtt_host main mqtt_host 'wss://cheburcheck.ru/mqtt' + config_get mqtt_port main mqtt_port '443' + config_get connection_timeout main connection_timeout '30' + config_get max_concurrent_tasks main max_concurrent_tasks '8' + config_get traceroute_max_hops main traceroute_max_hops '5' + config_get traceroute_retries main traceroute_retries '3' + config_get traceroute_control_hosts main traceroute_control_hosts '3' + config_get log_level main log_level 'info' + + procd_open_instance + procd_set_param command /usr/bin/cheburprobe + procd_set_param respawn 3600 5 5 + procd_set_param env \ + PROBE_ID="$probe_id" \ + PROBE_TOKEN="$probe_token" \ + MQTT_HOST="$mqtt_host" \ + MQTT_PORT="$mqtt_port" \ + MQTT_CONNECTION_TIMEOUT_SECS="$connection_timeout" \ + MAX_CONCURRENT_TASKS="$max_concurrent_tasks" \ + TRACEROUTE_MAX_HOPS="$traceroute_max_hops" \ + TRACEROUTE_RETRIES="$traceroute_retries" \ + TRACEROUTE_CONTROL_HOSTS="$traceroute_control_hosts" \ + RUST_LOG="$log_level" + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_close_instance +} + +service_triggers() { + procd_add_reload_trigger cheburprobe +} + +reload_service() { + stop + start +} diff --git a/probe/openwrt/cheburprobe.postinst b/probe/openwrt/cheburprobe.postinst new file mode 100644 index 0000000..8989158 --- /dev/null +++ b/probe/openwrt/cheburprobe.postinst @@ -0,0 +1,5 @@ +#!/bin/sh + +[ -n "${IPKG_INSTROOT:-}" ] || /etc/init.d/cheburprobe enable +exit 0 + diff --git a/probe/openwrt/luci/acl.json b/probe/openwrt/luci/acl.json new file mode 100644 index 0000000..d5ec4c7 --- /dev/null +++ b/probe/openwrt/luci/acl.json @@ -0,0 +1,8 @@ +{ + "luci-app-cheburprobe": { + "description": "Manage Cheburprobe configuration", + "read": { "uci": [ "cheburprobe" ] }, + "write": { "uci": [ "cheburprobe" ] } + } +} + diff --git a/probe/openwrt/luci/config.js b/probe/openwrt/luci/config.js new file mode 100644 index 0000000..39cfa4e --- /dev/null +++ b/probe/openwrt/luci/config.js @@ -0,0 +1,69 @@ +'use strict'; +'require view'; +'require form'; +'require uci'; + +return view.extend({ + load: function() { + return uci.load('cheburprobe'); + }, + + render: function() { + var m, s, o; + + m = new form.Map('cheburprobe', _('Cheburprobe'), + _('Configure the Cheburcheck dynamic network probe. Changes are applied to the service automatically.')); + s = m.section(form.NamedSection, 'main', 'cheburprobe', _('Service settings')); + s.addremove = false; + + o = s.option(form.Flag, 'enabled', _('Enable service')); + o.rmempty = false; + + o = s.option(form.Value, 'probe_id', _('Probe ID')); + o.datatype = 'uinteger'; + o.rmempty = false; + + o = s.option(form.Value, 'probe_token', _('Probe token')); + o.password = true; + o.rmempty = false; + + o = s.option(form.Value, 'mqtt_host', _('MQTT WebSocket URL')); + o.default = 'wss://cheburcheck.ru/mqtt'; + o.rmempty = false; + + o = s.option(form.Value, 'mqtt_port', _('MQTT port')); + o.datatype = 'port'; + o.default = '443'; + o.rmempty = false; + + o = s.option(form.Value, 'connection_timeout', _('Connection timeout'), _('Seconds')); + o.datatype = 'and(uinteger,min(1))'; + o.default = '30'; + + o = s.option(form.Value, 'max_concurrent_tasks', _('Maximum concurrent tasks')); + o.datatype = 'and(uinteger,min(1))'; + o.default = '8'; + + o = s.option(form.Value, 'traceroute_max_hops', _('Traceroute maximum hops')); + o.datatype = 'and(uinteger,min(1))'; + o.default = '5'; + + o = s.option(form.Value, 'traceroute_retries', _('Traceroute retries')); + o.datatype = 'and(uinteger,min(1))'; + o.default = '3'; + + o = s.option(form.Value, 'traceroute_control_hosts', _('Traceroute control hosts')); + o.datatype = 'and(uinteger,min(1))'; + o.default = '3'; + + o = s.option(form.ListValue, 'log_level', _('Log level')); + o.value('error', _('Error')); + o.value('warn', _('Warning')); + o.value('info', _('Info')); + o.value('debug', _('Debug')); + o.value('trace', _('Trace')); + o.default = 'info'; + + return m.render(); + } +}); diff --git a/probe/openwrt/luci/menu.json b/probe/openwrt/luci/menu.json new file mode 100644 index 0000000..5d9a49e --- /dev/null +++ b/probe/openwrt/luci/menu.json @@ -0,0 +1,9 @@ +{ + "admin/services/cheburprobe": { + "title": "Cheburprobe", + "order": 90, + "action": { "type": "view", "path": "cheburprobe/config" }, + "depends": { "acl": [ "luci-app-cheburprobe" ] } + } +} +