diff --git a/.github/workflows/build-legacy.yml b/.github/workflows/build-legacy.yml
index 6248028..71529e1 100644
--- a/.github/workflows/build-legacy.yml
+++ b/.github/workflows/build-legacy.yml
@@ -184,66 +184,62 @@ jobs:
while IFS= read -r file; do
artifact="$(basename "$(dirname "$file")")"
cp "$file" "legacy-publish/${artifact}-$(basename "$file")"
- done < <(find legacy-artifacts -type f ! -name 'SHA256SUMS' -print | sort)
+ done < <(find legacy-artifacts -type f \( -name '*.ipk' -o -name '*.apk' \) -print | sort)
+ find legacy-publish -type f -print -quit | grep -q .
- name: Attest legacy release assets
uses: actions/attest-build-provenance@v2
with:
subject-path: legacy-publish/*
- - name: Combine and reconstruct all release package artifacts
+ - name: Download current release build artifacts
env:
AGENT_TAG: ${{ inputs.agent_tag }}
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
- mkdir release-assets
- gh release download "$AGENT_TAG" \
+ release_run_id="$(
+ gh run list \
+ --repo "$GITHUB_REPOSITORY" \
+ --workflow build.yml \
+ --branch "$AGENT_TAG" \
+ --event push \
+ --status success \
+ --limit 20 \
+ --json databaseId \
+ --jq '.[0].databaseId'
+ )"
+ test -n "$release_run_id"
+ gh run download "$release_run_id" \
--repo "$GITHUB_REPOSITORY" \
--pattern 'openwrt-*' \
- --dir release-assets
- cp legacy-publish/* release-assets/
- bash scripts/unpack-release-package-assets.sh \
- release-assets \
- .github/openwrt-sdk-lock.tsv \
- combined-artifacts
+ --dir current-artifacts
+
+ - name: Prepare combined signed package repository
+ env:
+ AGENT_TAG: ${{ inputs.agent_tag }}
+ shell: bash
+ run: |
+ set -euo pipefail
+ mkdir combined-artifacts
+ cp -a current-artifacts/openwrt-* combined-artifacts/
+ cp -a legacy-artifacts/openwrt-* combined-artifacts/
bash scripts/prepare-package-repository.sh \
combined-artifacts \
package-repository \
"${AGENT_TAG#agent-v}"
- - uses: sigstore/cosign-installer@v3
-
- - name: Prepare combined release checksums
- shell: bash
- run: |
- set -euo pipefail
- (
- cd release-assets
- sha256sum ./openwrt-* > SHA256SUMS
- )
-
- - name: Attest combined release checksums
- uses: actions/attest-build-provenance@v2
- with:
- subject-path: release-assets/SHA256SUMS
-
- - name: Sign and upload combined release checksums
+ - name: Upload legacy packages
env:
AGENT_TAG: ${{ inputs.agent_tag }}
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
- cosign sign-blob --yes \
- --bundle release-assets/SHA256SUMS.sigstore.json \
- release-assets/SHA256SUMS
mapfile -t legacy_files < <(find legacy-publish -type f -print | sort)
gh release upload "$AGENT_TAG" \
"${legacy_files[@]}" \
- release-assets/SHA256SUMS \
- release-assets/SHA256SUMS.sigstore.json \
--repo "$GITHUB_REPOSITORY" \
--clobber
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 677758c..146556a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -208,7 +208,7 @@ jobs:
name: openwrt-${{ matrix.release }}-${{ matrix.label }}
path: artifacts/*
if-no-files-found: error
- retention-days: 30
+ retention-days: 90
release:
name: Publish agent GitHub release assets
@@ -233,8 +233,17 @@ jobs:
while IFS= read -r file; do
artifact="$(basename "$(dirname "$file")")"
cp "$file" "publish/${artifact}-$(basename "$file")"
- done < <(find release-assets -type f ! -name 'SHA256SUMS' -print | sort)
- (cd publish && sha256sum ./* > SHA256SUMS)
+ done < <(find release-assets -type f \( -name '*.ipk' -o -name '*.apk' \) -print | sort)
+ find publish -type f -print -quit | grep -q .
+
+ - name: Prepare release notes
+ shell: bash
+ run: |
+ set -euo pipefail
+ bash scripts/extract-release-notes.sh \
+ "$GITHUB_REF_NAME" \
+ CHANGELOG.md \
+ release-notes.md
- name: Prepare signed package repository
shell: bash
@@ -257,16 +266,6 @@ jobs:
with:
subject-path: publish/*
- - uses: sigstore/cosign-installer@v3
-
- - name: Sign release checksums with GitHub OIDC
- shell: bash
- run: |
- set -euo pipefail
- cosign sign-blob --yes \
- --bundle publish/SHA256SUMS.sigstore.json \
- publish/SHA256SUMS
-
- name: Create release and upload packages
env:
GH_TOKEN: ${{ github.token }}
@@ -278,10 +277,13 @@ jobs:
gh release upload "${GITHUB_REF_NAME}" "${files[@]}" \
--repo "${GITHUB_REPOSITORY}" \
--clobber
+ gh release edit "${GITHUB_REF_NAME}" \
+ --repo "${GITHUB_REPOSITORY}" \
+ --notes-file release-notes.md
else
gh release create "${GITHUB_REF_NAME}" "${files[@]}" \
--repo "${GITHUB_REPOSITORY}" \
- --generate-notes \
+ --notes-file release-notes.md \
--title "OpenWrt RMM Agent ${GITHUB_REF_NAME#agent-v}"
fi
diff --git a/.github/workflows/release-server.yml b/.github/workflows/release-server.yml
index 3ace70d..bd04f5c 100644
--- a/.github/workflows/release-server.yml
+++ b/.github/workflows/release-server.yml
@@ -132,9 +132,17 @@ jobs:
shell: bash
run: |
set -euo pipefail
- if ! gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
+ bash scripts/extract-release-notes.sh \
+ "$GITHUB_REF_NAME" \
+ CHANGELOG.md \
+ release-notes.md
+ if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
+ gh release edit "$GITHUB_REF_NAME" \
+ --repo "$GITHUB_REPOSITORY" \
+ --notes-file release-notes.md
+ else
gh release create "$GITHUB_REF_NAME" \
--repo "$GITHUB_REPOSITORY" \
- --generate-notes \
+ --notes-file release-notes.md \
--title "OpenWrt RMM Server ${GITHUB_REF_NAME#server-v}"
fi
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..b242be5
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,57 @@
+# Changelog
+
+This file contains user-facing release notes. Every section must match its Git tag;
+the release workflow fails when notes for a new tag have not been prepared.
+
+## agent-v0.6.8
+
+Stable agent release with more reliable LAN client presence detection.
+
+### Added
+
+- The agent safely probes private IPv4 addresses found in DHCP leases and reports the
+ results with its heartbeat.
+- Probing is capped at 32 addresses, six concurrent requests, and a short timeout to
+ avoid noticeable load on the router or LAN.
+- The server stores `first_seen`, `last_seen`, and the most recent probe time for each
+ client.
+- The LuCI application keeps English as its default language and provides Russian through
+ the separate `luci-i18n-rmm-agent-ru` package.
+
+### Fixed
+
+- A static DHCP reservation is no longer treated as proof that a client is currently
+ connected.
+- Clients are separated into Online, Recently online, and Unconfirmed states.
+- DHCP, Wi-Fi, neighbor-table, and active-probe data are merged without duplicate
+ clients.
+
+### Compatibility
+
+- The API protocol remains `v1`; the server and agent can still be upgraded
+ independently.
+- Existing agent configuration remains compatible without changes.
+- The primary workflow builds OpenWrt 24.10 and 25.12 packages. OpenWrt 21.02, 22.03,
+ and 23.05 packages are added by the separate legacy workflow.
+
+### Installation
+
+Download the `.ipk` or `.apk` matching the OpenWrt release and target architecture.
+Install `luci-app-rmm-agent` as well to configure the agent through LuCI. Install
+`luci-i18n-rmm-agent-ru` for the Russian interface.
+
+## server-v0.8.1
+
+Corrective server release for cloud SSH and LuCI tunnels.
+
+### Fixed
+
+- The server validates SSH and LuCI ports issued to agents and rejects invalid
+ endpoints.
+- Reverse-tunnel parameters are validated before they are exposed to an operator.
+- Boundary-value and malformed tunnel-service response tests were added.
+
+### Compatibility
+
+- No database migration is required.
+- The agent protocol remains `v1`.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d269973
--- /dev/null
+++ b/README.md
@@ -0,0 +1,139 @@
+
+
+
+
+# OpenWrt RMM
+
+[Русская версия](README_RU.md) · [Releases](https://github.com/Benya9669/openwrt-rmm/releases) · [Changelog](CHANGELOG.md) · [Security](docs/security.md)
+
+OpenWrt RMM is a self-hosted remote monitoring and management platform for OpenWrt
+routers. It combines a Go cloud server, a lightweight outbound agent, a responsive web
+dashboard, temporary SSH/LuCI access, notifications, and signed OpenWrt package feeds.
+
+The agent does not require an inbound port on the router. It connects to the server over
+HTTPS and opens a restricted reverse tunnel only when an authorized operator requests
+remote access.
+
+## Highlights
+
+- Separate user accounts and router ownership.
+- One-time router enrollment grants.
+- Live inventory, health metrics, connectivity checks, and LAN client presence.
+- Safe allowlisted diagnostics and package/UCI operations.
+- Temporary SSH and LuCI access through an isolated cloud tunnel.
+- Built-in notification center, e-mail, Telegram, and signed webhooks.
+- Quiet hours, maintenance pauses, and per-router alert preferences.
+- Responsive web UI and a LuCI application for agent configuration.
+- Signed IPK/APK repositories for supported OpenWrt releases.
+
+## Components
+
+```text
+Browser ──HTTPS──> RMM server ──SQLite
+ │
+ ├── notification workers
+ └── isolated SSH tunnel service
+ ▲
+OpenWrt router ──outbound HTTPS/SSH───┘
+ ├── rmm-agent-go-production
+ ├── luci-app-rmm-agent
+ └── luci-i18n-rmm-agent-ru (optional)
+```
+
+| Component | Purpose | License |
+| --- | --- | --- |
+| `server/`, `web/` | API, persistence, dashboard, notifications | AGPL-3.0-only |
+| `deploy/tunnel/` | Restricted reverse SSH tunnel service | AGPL-3.0-only |
+| `agent/` | Go agent, LuCI app, and OpenWrt packages | MIT |
+
+## Deployment
+
+Requirements:
+
+- Docker Engine with Compose v2;
+- an HTTPS reverse proxy such as NPMplus;
+- a wildcard DNS record and certificate for cloud LuCI access;
+- an SSH key dedicated to router tunnels.
+
+Copy the example configuration and follow the production deployment guide:
+
+```sh
+cp .env.example .env
+docker compose -f compose.yaml -f compose.release.yaml pull
+docker compose -f compose.yaml -f compose.release.yaml up -d
+docker compose -f compose.yaml -f compose.release.yaml ps
+```
+
+Do not start production with the example secrets. Generate the tunnel key and configure
+the required environment values first.
+
+- [Docker Compose deployment](docs/docker-compose.md)
+- [NPMplus configuration](docs/npmplus.md)
+- [KeenDNS-like wildcard router access](docs/keendns.md)
+- [Architecture](docs/architecture.md)
+
+## Installing the OpenWrt agent
+
+The recommended method is the signed package feed:
+
+- OpenWrt 24.10 and older: IPK/opkg;
+- OpenWrt 25.12 and newer: APK;
+- OpenWrt 21.02, 22.03, and 23.05: legacy support tier.
+
+Install the runtime and LuCI application:
+
+```sh
+opkg install rmm-agent-go-production luci-app-rmm-agent
+# or
+apk add rmm-agent-go-production luci-app-rmm-agent
+```
+
+English is the default LuCI language. Install the optional Russian translation:
+
+```sh
+opkg install luci-i18n-rmm-agent-ru
+# or
+apk add luci-i18n-rmm-agent-ru
+```
+
+See [Signed OpenWrt package repository](docs/package-repository.md) for feed URLs,
+verification keys, and architecture-specific instructions.
+
+## Development
+
+```sh
+go test ./...
+go vet ./...
+node --check web/app.js
+docker compose config
+```
+
+Run the server in explicit local development mode:
+
+```sh
+RMM_INSECURE_DEV_MODE=true \
+RMM_OPERATOR_PASSWORD='replace-with-a-long-development-password' \
+go run ./server/cmd/rmm-server
+```
+
+Never enable insecure development mode on an internet-facing deployment.
+
+## Releases
+
+The server and agent use independent version lines:
+
+- `server-v*` publishes the server and tunnel container images;
+- `agent-v*` publishes installable IPK/APK packages;
+- release descriptions are maintained in English in [CHANGELOG.md](CHANGELOG.md).
+
+GitHub Releases contain only installable packages. Signed repository indexes and public
+keys are published through GitHub Pages, while provenance is kept in GitHub attestations.
+See [Release policy](RELEASES.md).
+
+## Security and license
+
+Review [the security model](docs/security.md) before exposing the service publicly.
+Back up the SQLite volume before every server upgrade.
+
+The cloud application is licensed under `AGPL-3.0-only`; the OpenWrt agent and LuCI
+packages are licensed under MIT. See [LICENSE.md](LICENSE.md) and [NOTICE.md](NOTICE.md).
diff --git a/README_RU.md b/README_RU.md
new file mode 100644
index 0000000..714b659
--- /dev/null
+++ b/README_RU.md
@@ -0,0 +1,140 @@
+
+
+
+
+# OpenWrt RMM
+
+[English](README.md) · [Релизы](https://github.com/Benya9669/openwrt-rmm/releases) · [Changelog](CHANGELOG.md) · [Безопасность](docs/security.md)
+
+OpenWrt RMM — самостоятельная платформа удалённого мониторинга и управления роутерами
+OpenWrt. В неё входят облачный сервер на Go, компактный исходящий агент, адаптивный
+веб-интерфейс, временный доступ по SSH и LuCI, уведомления и подписанные репозитории
+пакетов OpenWrt.
+
+На роутере не требуется открывать входящий порт. Агент подключается к серверу по HTTPS,
+а ограниченный обратный туннель создаётся только по запросу авторизованного оператора.
+
+## Возможности
+
+- Отдельные аккаунты пользователей и привязка роутеров к владельцу.
+- Одноразовые коды подключения роутеров.
+- Инвентаризация, метрики, проверки соединения и определение присутствия LAN-клиентов.
+- Безопасный ограниченный набор диагностических, пакетных и UCI-команд.
+- Временный доступ к SSH и LuCI через изолированный облачный туннель.
+- Центр уведомлений, e-mail, Telegram и подписанные webhook-запросы.
+- Тихие часы, режим обслуживания и настройки алертов для каждого роутера.
+- Адаптивный веб-интерфейс и LuCI-приложение для настройки агента.
+- Подписанные IPK/APK-репозитории для поддерживаемых версий OpenWrt.
+
+## Компоненты
+
+```text
+Браузер ──HTTPS──> RMM-сервер ──SQLite
+ │
+ ├── обработчики уведомлений
+ └── изолированный SSH tunnel-сервис
+ ▲
+OpenWrt-роутер ──исходящие HTTPS/SSH─────┘
+ ├── rmm-agent-go-production
+ ├── luci-app-rmm-agent
+ └── luci-i18n-rmm-agent-ru (необязательно)
+```
+
+| Компонент | Назначение | Лицензия |
+| --- | --- | --- |
+| `server/`, `web/` | API, база данных, интерфейс и уведомления | AGPL-3.0-only |
+| `deploy/tunnel/` | Ограниченный обратный SSH-туннель | AGPL-3.0-only |
+| `agent/` | Go-агент, LuCI и пакеты OpenWrt | MIT |
+
+## Развёртывание
+
+Потребуются:
+
+- Docker Engine и Compose v2;
+- HTTPS reverse proxy, например NPMplus;
+- wildcard DNS-запись и сертификат для облачного доступа к LuCI;
+- отдельный SSH-ключ для туннелей роутеров.
+
+Скопируйте пример конфигурации и следуйте инструкции по развёртыванию:
+
+```sh
+cp .env.example .env
+docker compose -f compose.yaml -f compose.release.yaml pull
+docker compose -f compose.yaml -f compose.release.yaml up -d
+docker compose -f compose.yaml -f compose.release.yaml ps
+```
+
+Не запускайте прод с примерными секретами. Сначала создайте ключ туннеля и заполните
+обязательные переменные окружения.
+
+- [Развёртывание через Docker Compose](docs/docker-compose.md)
+- [Настройка NPMplus](docs/npmplus.md)
+- [Wildcard-доступ к роутерам в стиле KeenDNS](docs/keendns.md)
+- [Архитектура](docs/architecture.md)
+
+## Установка агента OpenWrt
+
+Рекомендуемый способ — подписанный пакетный репозиторий:
+
+- OpenWrt 24.10 и старше: IPK/opkg;
+- OpenWrt 25.12 и новее: APK;
+- OpenWrt 21.02, 22.03 и 23.05: уровень legacy-поддержки.
+
+Установите агент и LuCI-приложение:
+
+```sh
+opkg install rmm-agent-go-production luci-app-rmm-agent
+# или
+apk add rmm-agent-go-production luci-app-rmm-agent
+```
+
+По умолчанию интерфейс LuCI остаётся английским. Русский язык устанавливается отдельным
+стандартным пакетом:
+
+```sh
+opkg install luci-i18n-rmm-agent-ru
+# или
+apk add luci-i18n-rmm-agent-ru
+```
+
+Адреса репозиториев, ключи проверки и инструкции для архитектур находятся в документе
+[Подписанный репозиторий OpenWrt](docs/package-repository.md).
+
+## Разработка и проверки
+
+```sh
+go test ./...
+go vet ./...
+node --check web/app.js
+docker compose config
+```
+
+Запуск сервера в явно включённом локальном режиме разработки:
+
+```sh
+RMM_INSECURE_DEV_MODE=true \
+RMM_OPERATOR_PASSWORD='replace-with-a-long-development-password' \
+go run ./server/cmd/rmm-server
+```
+
+Небезопасный режим разработки нельзя использовать на публичном сервере.
+
+## Релизы
+
+Сервер и агент версионируются независимо:
+
+- `server-v*` публикует контейнеры сервера и tunnel-сервиса;
+- `agent-v*` публикует устанавливаемые IPK/APK-пакеты;
+- подробные описания релизов ведутся на английском в [CHANGELOG.md](CHANGELOG.md).
+
+В GitHub Release загружаются только устанавливаемые пакеты. Подписанные индексы и
+публичные ключи публикуются через GitHub Pages, а provenance хранится в GitHub
+attestations. Подробнее: [политика релизов](RELEASES.md).
+
+## Безопасность и лицензирование
+
+Перед публикацией сервиса изучите [модель безопасности](docs/security.md). Перед каждым
+обновлением серверной части создавайте резервную копию SQLite volume.
+
+Облачная часть распространяется по `AGPL-3.0-only`, агент и LuCI-пакеты — по MIT.
+Подробнее: [LICENSE.md](LICENSE.md) и [NOTICE.md](NOTICE.md).
diff --git a/RELEASES.md b/RELEASES.md
index b0a4cf7..dcc2dd9 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -62,22 +62,27 @@ transition period; it should not silently reuse `v1`.
## Release commands
+Before creating a tag, add an English `## ` section to `CHANGELOG.md`. The release
+workflow uses that section verbatim and fails instead of publishing empty autogenerated
+notes.
+
```sh
-git tag -a server-v0.8.1 -m "OpenWrt RMM Server 0.8.1"
+git tag -s server-v0.8.1 -m "OpenWrt RMM Server 0.8.1"
git push origin server-v0.8.1
-git tag -a agent-v0.6.8 -m "OpenWrt RMM Agent 0.6.8"
+git tag -s agent-v0.6.8 -m "OpenWrt RMM Agent 0.6.8"
git push origin agent-v0.6.8
```
Pushing a server tag publishes the container image and creates a GitHub Release. Pushing
-an agent tag builds the full OpenWrt matrix and creates a GitHub Release with packages and
-checksums. It also publishes signed package feeds through GitHub Pages. Repository setup,
-key generation and router configuration are documented in
+an agent tag builds the full OpenWrt matrix and creates a GitHub Release containing only
+installable `.ipk` and `.apk` packages. Repository indexes, public signing keys and
+provenance metadata stay in the signed GitHub Pages package feed and GitHub attestations.
+Repository setup, key generation and router configuration are documented in
[`docs/package-repository.md`](docs/package-repository.md). `workflow_dispatch` can test
either workflow without creating a release.
-Server and tunnel images are published with SBOM/provenance attestations and keyless
-Sigstore signatures bound to the release workflow identity. Agent checksums receive the
-same GitHub OIDC-backed signature. IPK and APK feeds additionally use their native OpenWrt
-repository signatures so `opkg` and `apk` can enforce trust on the router.
+Server and tunnel images are published with SBOM/provenance attestations bound to the
+release workflow identity. Agent packages receive GitHub build-provenance attestations.
+IPK and APK feeds additionally use their native OpenWrt repository signatures so `opkg`
+and `apk` can enforce trust on the router.
diff --git a/agent/README.md b/agent/README.md
index 272ce24..ccf0a71 100644
--- a/agent/README.md
+++ b/agent/README.md
@@ -7,6 +7,10 @@ Production package upgrades restart an already running agent so the new binary t
Production Go agent for OpenWrt, with the shell implementation retained as a fallback runtime.
+The LuCI application uses English by default. Russian is distributed as the standard
+optional `luci-i18n-rmm-agent-ru` package so the router can select either language
+through its normal LuCI language settings.
+
The agent, LuCI application and OpenWrt packaging under `agent/` are licensed under the
MIT License. Binary OpenWrt packages install a copy of the license under
`/usr/share/licenses//LICENSE`.
diff --git a/agent/package/luci-app-rmm-agent/Makefile b/agent/package/luci-app-rmm-agent/Makefile
index 7ef3dcb..c52a8e5 100644
--- a/agent/package/luci-app-rmm-agent/Makefile
+++ b/agent/package/luci-app-rmm-agent/Makefile
@@ -1,44 +1,16 @@
# SPDX-License-Identifier: MIT
include $(TOPDIR)/rules.mk
-PKG_NAME:=luci-app-rmm-agent
-PKG_VERSION:=0.2.1
-PKG_RELEASE:=2
+PKG_VERSION:=0.2.2
+PKG_RELEASE:=1
PKG_MAINTAINER:=RMM OpenWrt
PKG_LICENSE:=MIT
PKG_LICENSE_FILES:=LICENSE
-include $(INCLUDE_DIR)/package.mk
+LUCI_TITLE:=OpenWrt RMM agent configuration
+LUCI_DESCRIPTION:=Configure and control the OpenWrt RMM agent.
+LUCI_DEPENDS:=+luci-base
+LUCI_PKGARCH:=all
-define Package/luci-app-rmm-agent
- SECTION:=luci
- CATEGORY:=LuCI
- SUBMENU:=3. Applications
- TITLE:=LuCI support for the OpenWrt RMM agent
- DEPENDS:=+luci-base
- PKGARCH:=all
-endef
-
-define Package/luci-app-rmm-agent/description
- LuCI application for configuring and controlling the OpenWrt RMM agent.
-endef
-
-define Build/Compile
-endef
-
-define Package/luci-app-rmm-agent/install
- $(INSTALL_DIR) $(1)/www/luci-static/resources/view/services
- $(INSTALL_DATA) ./htdocs/luci-static/resources/view/services/rmm-agent.js \
- $(1)/www/luci-static/resources/view/services/rmm-agent.js
- $(INSTALL_DIR) $(1)/usr/share/luci/menu.d
- $(INSTALL_DATA) ./root/usr/share/luci/menu.d/luci-app-rmm-agent.json \
- $(1)/usr/share/luci/menu.d/luci-app-rmm-agent.json
- $(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
- $(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/luci-app-rmm-agent.json \
- $(1)/usr/share/rpcd/acl.d/luci-app-rmm-agent.json
- $(INSTALL_DIR) $(1)/usr/share/licenses/luci-app-rmm-agent
- $(INSTALL_DATA) ./LICENSE $(1)/usr/share/licenses/luci-app-rmm-agent/LICENSE
-endef
-
-$(eval $(call BuildPackage,luci-app-rmm-agent))
+include $(TOPDIR)/feeds/luci/luci.mk
diff --git a/agent/package/luci-app-rmm-agent/README.md b/agent/package/luci-app-rmm-agent/README.md
index f436041..459f407 100644
--- a/agent/package/luci-app-rmm-agent/README.md
+++ b/agent/package/luci-app-rmm-agent/README.md
@@ -27,6 +27,16 @@ opkg install rmm-agent_*.ipk luci-app-rmm-agent_*.ipk
On apk-based OpenWrt releases, install the generated `.apk` packages instead.
+English remains the built-in default language. The same build produces the standard
+optional Russian translation package:
+
+```text
+luci-i18n-rmm-agent-ru
+```
+
+After it is installed, LuCI selects Russian according to the router's configured
+interface language.
+
## Reproducible Docker build
The repository includes a containerized OpenWrt SDK build. By default it uses the
diff --git a/agent/package/luci-app-rmm-agent/po/ru/rmm-agent.po b/agent/package/luci-app-rmm-agent/po/ru/rmm-agent.po
new file mode 100644
index 0000000..528d5bd
--- /dev/null
+++ b/agent/package/luci-app-rmm-agent/po/ru/rmm-agent.po
@@ -0,0 +1,81 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: luci-app-rmm-agent\n"
+"Language: ru\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : "
+"n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+msgid "RMM agent"
+msgstr "RMM Агент"
+
+msgid "Command failed with code %d"
+msgstr "Команда завершилась с кодом %d"
+
+msgid "RMM agent action completed."
+msgstr "Действие RMM Агента выполнено."
+
+msgid "Unable to control RMM agent: %s"
+msgstr "Не удалось управлять RMM Агентом: %s"
+
+msgid "Connect this router to your RMM account. Create a one-time enrollment grant in the RMM control panel, paste it below, save and restart the agent."
+msgstr "Подключите этот роутер к своей учётной записи RMM. Создайте одноразовый код подключения в панели RMM, вставьте его ниже, сохраните настройки и перезапустите агент."
+
+msgid "Connection"
+msgstr "Подключение"
+
+msgid "Enable agent"
+msgstr "Включить агент"
+
+msgid "RMM server URL"
+msgstr "Адрес сервера RMM"
+
+msgid "Use an HTTPS URL, or explicitly allow insecure HTTP in advanced settings."
+msgstr "Используйте адрес HTTPS или явно разрешите небезопасный HTTP в дополнительных настройках."
+
+msgid "One-time enrollment grant"
+msgstr "Одноразовый код подключения"
+
+msgid "The grant is consumed once and removed from the router after successful enrollment."
+msgstr "Код используется один раз и удаляется с роутера после успешного подключения."
+
+msgid "Polling interval"
+msgstr "Интервал опроса"
+
+msgid "Connectivity check targets"
+msgstr "Адреса для проверки соединения"
+
+msgid "Advanced settings"
+msgstr "Дополнительные настройки"
+
+msgid "Tunnel identity file"
+msgstr "Файл ключа туннеля"
+
+msgid "Allow insecure HTTP"
+msgstr "Разрешить небезопасный HTTP"
+
+msgid "Use only in an isolated lab. Agent credentials can otherwise be intercepted."
+msgstr "Используйте только в изолированной тестовой сети. Иначе учётные данные агента могут быть перехвачены."
+
+msgid "Re-enroll on next restart"
+msgstr "Подключить заново при следующем запуске"
+
+msgid "Deletes the current device identity. Create and enter a fresh one-time grant first."
+msgstr "Удаляет текущую идентификацию устройства. Сначала создайте и введите новый одноразовый код подключения."
+
+msgid "Service status"
+msgstr "Состояние службы"
+
+msgid "The RMM agent is running."
+msgstr "RMM Агент запущен."
+
+msgid "The RMM agent is stopped."
+msgstr "RMM Агент остановлен."
+
+msgid "Restart agent"
+msgstr "Перезапустить агент"
+
+msgid "Start agent"
+msgstr "Запустить агент"
diff --git a/deploy/luci-builder/Dockerfile b/deploy/luci-builder/Dockerfile
index a31a3f7..6170de9 100644
--- a/deploy/luci-builder/Dockerfile
+++ b/deploy/luci-builder/Dockerfile
@@ -141,10 +141,13 @@ RUN rm -rf tmp/info \
tmp/.packagedeps \
tmp/.packageinfo
-RUN printf '%s\n' 'CONFIG_PACKAGE_luci-app-rmm-agent=m' >> .config \
+RUN printf '%s\n' \
+ 'CONFIG_PACKAGE_luci-app-rmm-agent=m' \
+ 'CONFIG_PACKAGE_luci-i18n-rmm-agent-ru=m' \
+ >> .config \
&& make defconfig \
&& awk ' \
- /^CONFIG_PACKAGE_(ca-bundle|ip-tiny|iwinfo|libopenssl|luci-app-rmm-agent|openssh-client|openssh-keygen|rmm-agent-go-production|zlib)=m$/ { \
+ /^CONFIG_PACKAGE_(ca-bundle|ip-tiny|iwinfo|libopenssl|luci-app-rmm-agent|luci-i18n-rmm-agent-ru|openssh-client|openssh-keygen|rmm-agent-go-production|zlib)=m$/ { \
print; \
next; \
} \
@@ -156,7 +159,8 @@ RUN printf '%s\n' 'CONFIG_PACKAGE_luci-app-rmm-agent=m' >> .config \
{ print; } \
' .config > .config.packages-pruned \
&& mv .config.packages-pruned .config \
- && grep -q '^CONFIG_PACKAGE_luci-app-rmm-agent=m$' .config
+ && grep -q '^CONFIG_PACKAGE_luci-app-rmm-agent=m$' .config \
+ && grep -q '^CONFIG_PACKAGE_luci-i18n-rmm-agent-ru=m$' .config
RUN make -j1 package/luci-app-rmm-agent/compile V=s
@@ -171,10 +175,13 @@ RUN --mount=type=secret,id=openwrt_usign_secret_b64,uid=1000,required=false \
find bin -type f \
\( -name 'luci-app-rmm-agent*.apk' \
-o -name 'luci-app-rmm-agent*.ipk' \
+ -o -name 'luci-i18n-rmm-agent-ru*.apk' \
+ -o -name 'luci-i18n-rmm-agent-ru*.ipk' \
-o -name 'rmm-agent*.apk' \
-o -name 'rmm-agent*.ipk' \) \
-exec cp '{}' /home/builder/artifacts/ \;; \
test -n "$(find /home/builder/artifacts -maxdepth 1 -type f \( -name '*.apk' -o -name '*.ipk' \) -print -quit)"; \
+ test -n "$(find /home/builder/artifacts -maxdepth 1 -type f \( -name 'luci-i18n-rmm-agent-ru*.apk' -o -name 'luci-i18n-rmm-agent-ru*.ipk' \) -print -quit)"; \
cd /home/builder/artifacts; \
if find . -maxdepth 1 -type f -name '*.ipk' -print -quit | grep -q .; then \
MKHASH=/home/builder/sdk/staging_dir/host/bin/mkhash \
diff --git a/docs/package-repository.md b/docs/package-repository.md
index 65ae4c9..420c8cf 100644
--- a/docs/package-repository.md
+++ b/docs/package-repository.md
@@ -86,10 +86,14 @@ Run it only after the main agent release has completed. The legacy workflow:
1. checks out and builds the exact agent tag;
2. signs the IPK repositories with the configured `usign` key;
-3. combines them with the current release and reconstructs the complete repository;
-4. refreshes signed checksums and uploads the legacy files to the existing GitHub Release;
+3. downloads retained artifacts from the original tagged workflow and reconstructs the
+ complete repository;
+4. uploads only installable legacy packages to the existing GitHub Release;
5. redeploys the complete current plus legacy repository to GitHub Pages.
+Run the legacy workflow within 90 days of the tagged build while its internal artifacts
+are retained.
+
The workflow refuses an older agent tag because publishing it would roll the `stable`
feed back from the latest agent version.
@@ -112,6 +116,8 @@ wget -O "/etc/opkg/keys/${key_id}" "${key_base}/${key_id}"
printf 'src/gz rmm %s\n' "$feed" > /etc/opkg/customfeeds.conf.d/rmm.conf
opkg update
opkg install rmm-agent-go-production luci-app-rmm-agent
+# Optional Russian LuCI translation:
+opkg install luci-i18n-rmm-agent-ru
```
The workflow creates `Packages`, `Packages.gz` and `Packages.sig`. `opkg` verifies the
@@ -130,6 +136,8 @@ wget -O /etc/apk/keys/rmm-openwrt.pem "${base}/keys/apk/rmm-openwrt.pem"
printf '%s\n' "$repo" > /etc/apk/repositories.d/rmm.list
apk update
apk add rmm-agent-go-production luci-app-rmm-agent
+# Optional Russian LuCI translation:
+apk add luci-i18n-rmm-agent-ru
```
APK verifies the signed `packages.adb` index. Installation should not require
@@ -138,17 +146,8 @@ not configured correctly.
## Release verification
-Release assets also include a keyless Sigstore bundle for the combined checksums:
-
-```sh
-cosign verify-blob \
- --bundle SHA256SUMS.sigstore.json \
- --certificate-identity-regexp \
- '^https://github.com/Benya9669/openwrt-rmm/.github/workflows/(build\.yml@refs/tags/agent-v.*|build-legacy\.yml@refs/heads/main)$' \
- --certificate-oidc-issuer https://token.actions.githubusercontent.com \
- SHA256SUMS
-sha256sum --check SHA256SUMS
-```
-
-This Sigstore verification complements native package-manager trust; it does not replace
-the `usign` or APK repository signature.
+GitHub Releases contain only installable `.ipk` and `.apk` files. GitHub build-provenance
+attestations remain available through the repository attestations page and GitHub CLI.
+Package-manager installations rely on the signed feed: `opkg` verifies `Packages.sig`,
+while APK verifies `packages.adb`. Public verification keys are served through GitHub
+Pages and are not duplicated as release assets.
diff --git a/scripts/extract-release-notes.sh b/scripts/extract-release-notes.sh
new file mode 100644
index 0000000..f1b8b25
--- /dev/null
+++ b/scripts/extract-release-notes.sh
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+if [ "$#" -ne 3 ]; then
+ echo "usage: $0