build: add signed package repository releases
This commit is contained in:
@@ -4,6 +4,13 @@ RMM_INSECURE_DEV_MODE=false
|
||||
RMM_OPERATOR_USERNAME=admin
|
||||
RMM_OPERATOR_PASSWORD=replace-with-a-long-random-password
|
||||
|
||||
# Production release images. Pin an exact server version after the first
|
||||
# server-v* release; do not rely on latest for controlled upgrades.
|
||||
RMM_RELEASE_VERSION=latest
|
||||
RMM_SERVER_IMAGE=ghcr.io/benya9669/openwrt-rmm-server
|
||||
RMM_TUNNEL_IMAGE=ghcr.io/benya9669/openwrt-rmm-tunnel
|
||||
RMM_IMAGE_PULL_POLICY=always
|
||||
|
||||
# Optional emergency/API bearer token. Leave empty when browser sessions are enough.
|
||||
RMM_OPERATOR_TOKEN=
|
||||
|
||||
|
||||
@@ -148,6 +148,10 @@ jobs:
|
||||
|
||||
- name: Build agent and LuCI packages
|
||||
uses: docker/build-push-action@v6
|
||||
env:
|
||||
OPENWRT_USIGN_SECRET_B64: ${{ secrets.OPENWRT_USIGN_SECRET_B64 }}
|
||||
OPENWRT_USIGN_PUBLIC_B64: ${{ secrets.OPENWRT_USIGN_PUBLIC_B64 }}
|
||||
OPENWRT_APK_SECRET_B64: ${{ secrets.OPENWRT_APK_SECRET_B64 }}
|
||||
with:
|
||||
context: .
|
||||
file: deploy/luci-builder/Dockerfile
|
||||
@@ -161,6 +165,12 @@ jobs:
|
||||
RMM_GOARM=${{ matrix.goarm || '7' }}
|
||||
RMM_GOMIPS=${{ matrix.gomips || 'softfloat' }}
|
||||
RMM_GOAMD64=v1
|
||||
REQUIRE_NATIVE_SIGNATURE=${{ startsWith(github.ref, 'refs/tags/agent-v') }}
|
||||
REPOSITORY_INDEX_REVISION=${{ github.run_id }}
|
||||
secret-envs: |
|
||||
openwrt_usign_secret_b64=OPENWRT_USIGN_SECRET_B64
|
||||
openwrt_usign_public_b64=OPENWRT_USIGN_PUBLIC_B64
|
||||
openwrt_apk_secret_b64=OPENWRT_APK_SECRET_B64
|
||||
cache-from: type=gha,scope=openwrt-${{ matrix.release }}-${{ matrix.label }}
|
||||
cache-to: type=gha,mode=max,scope=openwrt-${{ matrix.release }}-${{ matrix.label }}
|
||||
|
||||
@@ -172,6 +182,20 @@ jobs:
|
||||
test -f artifacts/SHA256SUMS
|
||||
(cd artifacts && sha256sum --check SHA256SUMS)
|
||||
find artifacts -maxdepth 1 -type f \( -name '*.ipk' -o -name '*.apk' \) -print -quit | grep -q .
|
||||
if find artifacts -maxdepth 1 -type f -name '*.ipk' -print -quit | grep -q .; then
|
||||
test -f artifacts/Packages
|
||||
test -f artifacts/Packages.gz
|
||||
if [[ "$GITHUB_REF" == refs/tags/agent-v* ]]; then
|
||||
test -f artifacts/Packages.sig
|
||||
public_key="$(find artifacts -maxdepth 1 -type f -regextype posix-extended -regex '.*/[0-9a-f]{16}' -print -quit)"
|
||||
test -n "$public_key"
|
||||
fi
|
||||
else
|
||||
test -f artifacts/packages.adb
|
||||
if [[ "$GITHUB_REF" == refs/tags/agent-v* ]]; then
|
||||
test -f artifacts/rmm-openwrt.pem
|
||||
fi
|
||||
fi
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -187,14 +211,15 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: openwrt-*
|
||||
path: release-assets
|
||||
- name: Create release and upload packages
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
- name: Prepare release assets
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -202,8 +227,46 @@ jobs:
|
||||
while IFS= read -r file; do
|
||||
artifact="$(basename "$(dirname "$file")")"
|
||||
cp "$file" "publish/${artifact}-$(basename "$file")"
|
||||
done < <(find release-assets -type f \( -name '*.ipk' -o -name '*.apk' \) -print | sort)
|
||||
done < <(find release-assets -type f ! -name 'SHA256SUMS' -print | sort)
|
||||
(cd publish && sha256sum ./* > SHA256SUMS)
|
||||
|
||||
- name: Prepare signed package repository
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
bash scripts/prepare-package-repository.sh \
|
||||
release-assets \
|
||||
package-repository \
|
||||
"${GITHUB_REF_NAME#agent-v}"
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: package-repository-site
|
||||
path: package-repository
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
- name: Attest agent release assets
|
||||
uses: actions/attest-build-provenance@v2
|
||||
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 }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mapfile -t files < <(find publish -type f -print | sort)
|
||||
if gh release view "${GITHUB_REF_NAME}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
|
||||
gh release upload "${GITHUB_REF_NAME}" "${files[@]}" \
|
||||
@@ -215,3 +278,26 @@ jobs:
|
||||
--generate-notes \
|
||||
--title "OpenWrt RMM Agent ${GITHUB_REF_NAME#agent-v}"
|
||||
fi
|
||||
|
||||
package-repository:
|
||||
name: Publish signed package repository
|
||||
if: startsWith(github.ref, 'refs/tags/agent-v')
|
||||
needs: release
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
pages: write
|
||||
id-token: write
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: package-repository-site
|
||||
path: package-repository
|
||||
- uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: package-repository
|
||||
- name: Deploy package repository to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
|
||||
@@ -8,6 +8,8 @@ on:
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
|
||||
concurrency:
|
||||
group: server-release-${{ github.ref }}
|
||||
@@ -53,6 +55,7 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and optionally publish server image
|
||||
id: server_image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
@@ -69,8 +72,11 @@ jobs:
|
||||
org.opencontainers.image.licenses=AGPL-3.0-only
|
||||
cache-from: type=gha,scope=rmm-server
|
||||
cache-to: type=gha,mode=max,scope=rmm-server
|
||||
provenance: mode=max
|
||||
sbom: true
|
||||
|
||||
- name: Build and optionally publish tunnel image
|
||||
id: tunnel_image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: deploy/tunnel
|
||||
@@ -87,6 +93,37 @@ jobs:
|
||||
org.opencontainers.image.licenses=AGPL-3.0-only
|
||||
cache-from: type=gha,scope=rmm-tunnel
|
||||
cache-to: type=gha,mode=max,scope=rmm-tunnel
|
||||
provenance: mode=max
|
||||
sbom: true
|
||||
|
||||
- uses: sigstore/cosign-installer@v3
|
||||
if: startsWith(github.ref, 'refs/tags/server-v')
|
||||
|
||||
- name: Sign published images with GitHub OIDC
|
||||
if: startsWith(github.ref, 'refs/tags/server-v')
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cosign sign --yes \
|
||||
"ghcr.io/benya9669/openwrt-rmm-server@${{ steps.server_image.outputs.digest }}"
|
||||
cosign sign --yes \
|
||||
"ghcr.io/benya9669/openwrt-rmm-tunnel@${{ steps.tunnel_image.outputs.digest }}"
|
||||
|
||||
- name: Attest server image provenance
|
||||
if: startsWith(github.ref, 'refs/tags/server-v')
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-name: ghcr.io/benya9669/openwrt-rmm-server
|
||||
subject-digest: ${{ steps.server_image.outputs.digest }}
|
||||
push-to-registry: true
|
||||
|
||||
- name: Attest tunnel image provenance
|
||||
if: startsWith(github.ref, 'refs/tags/server-v')
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-name: ghcr.io/benya9669/openwrt-rmm-tunnel
|
||||
subject-digest: ${{ steps.tunnel_image.outputs.digest }}
|
||||
push-to-registry: true
|
||||
|
||||
- name: Create GitHub release
|
||||
if: startsWith(github.ref, 'refs/tags/server-v')
|
||||
|
||||
@@ -6,6 +6,7 @@ rmm.db
|
||||
/bin/
|
||||
/dist/
|
||||
/tmp/
|
||||
/.tmp/
|
||||
/.gocache/
|
||||
/.gopath/
|
||||
/rmm-server
|
||||
@@ -13,5 +14,6 @@ rmm.db
|
||||
*.exe~
|
||||
deploy/tunnel/data/
|
||||
secrets/
|
||||
release-keys/
|
||||
router_tunnel_key
|
||||
router_tunnel_key.pub
|
||||
|
||||
+8
-3
@@ -1,6 +1,6 @@
|
||||
# Актуальный инженерный checklist
|
||||
|
||||
Синхронизировано с кодом 2026-07-22. Источник продуктового порядка — `ROADMAP.md`.
|
||||
Синхронизировано с кодом 2026-07-23. Источник продуктового порядка — `ROADMAP.md`.
|
||||
|
||||
## Работает сейчас
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
|
||||
## Обязательное перед следующим production-релизом
|
||||
|
||||
- [ ] Сгенерировать и офлайн сохранить ключи `usign`/APK, добавить base64-значения в GitHub Actions Secrets.
|
||||
- [ ] Включить GitHub Pages из Actions и проверить первый подписанный package feed на реальном роутере.
|
||||
- [ ] Выпустить первый `server-v*`, проверить Cosign/provenance и закрепить `RMM_RELEASE_VERSION` в production `.env`.
|
||||
- [ ] Настроить SMTP и/или `RMM_TELEGRAM_BOT_TOKEN` в production environment.
|
||||
- [ ] Выполнить тестовую отправку из реального пользовательского профиля.
|
||||
- [ ] Проверить active → repeat → resolved на тестовом роутере.
|
||||
@@ -39,8 +42,10 @@
|
||||
- [ ] Подписанный webhook channel.
|
||||
- [ ] Quiet hours/timezone и per-device notification overrides.
|
||||
- [ ] Конфигурационные backup artifacts и retention.
|
||||
- [ ] CI для тестов, Docker, APK/IPK и release artifacts.
|
||||
- [ ] Signed update manifest и безопасное обновление агента.
|
||||
- [x] CI для тестов, Docker, multi-version APK/IPK и release artifacts.
|
||||
- [x] Keyless Cosign, provenance/SBOM контейнеров и подписанные checksum релизов.
|
||||
- [x] Нативные подписанные `Packages.sig`/`packages.adb` и публикация package feed.
|
||||
- [ ] Signed update manifest и безопасное обновление агента из кабинета.
|
||||
- [ ] Per-device tunnel credentials.
|
||||
- [ ] Organizations и расширенный RBAC.
|
||||
- [ ] MFA, command signatures и replay protection.
|
||||
|
||||
+9
-1
@@ -64,4 +64,12 @@ git push origin agent-v0.6.2
|
||||
|
||||
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. `workflow_dispatch` can test either workflow without creating a release.
|
||||
checksums. It also publishes signed package feeds through GitHub Pages. 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.
|
||||
|
||||
+9
-6
@@ -1,6 +1,6 @@
|
||||
# OpenWrt RMM — актуальный roadmap
|
||||
|
||||
Актуализировано: 2026-07-22. Текущая стабильная линия агента: `0.6.1`.
|
||||
Актуализировано: 2026-07-23. Текущая стабильная линия агента: `0.6.1`.
|
||||
|
||||
## Цель продукта
|
||||
|
||||
@@ -47,6 +47,8 @@ OpenWrt через исходящее соединение агента. Пол
|
||||
- [x] NPMplus overlay и wildcard device domain.
|
||||
- [x] Healthcheck, ограничение capabilities/resources и persistent volumes.
|
||||
- [x] SMTP STARTTLS/TLS и Telegram bot configuration через environment.
|
||||
- [x] Версионированные server/tunnel images в GHCR и production Compose overlay.
|
||||
- [x] SBOM/provenance, keyless Cosign и подписанный APK/IPK package repository.
|
||||
|
||||
## Следующие этапы
|
||||
|
||||
@@ -76,8 +78,9 @@ OpenWrt через исходящее соединение агента. Пол
|
||||
|
||||
### Этап 3 — обновления и release pipeline
|
||||
|
||||
- [ ] Подписанный release manifest агента и LuCI-приложения.
|
||||
- [ ] Автосборка APK/IPK и multi-architecture artifacts в CI.
|
||||
- [x] Автосборка APK/IPK и multi-architecture artifacts в CI.
|
||||
- [x] Нативная подпись IPK/APK feed, Cosign checksums и публикация репозитория.
|
||||
- [ ] Подписанный update manifest агента и LuCI-приложения.
|
||||
- [ ] Обновление одного роутера из кабинета.
|
||||
- [ ] Canary/поэтапный rollout с остановкой при потере связи.
|
||||
- [ ] История и безопасный rollback версии агента.
|
||||
@@ -107,6 +110,6 @@ OpenWrt через исходящее соединение агента. Пол
|
||||
|
||||
## Ближайший приоритет
|
||||
|
||||
1. Развернуть и проверить notification release на production.
|
||||
2. Добавить webhook и quiet hours.
|
||||
3. Начать конфигурационные backup/restore.
|
||||
1. Создать и сохранить ключи package feed, включить GitHub Pages и проверить первый подписанный agent/server release.
|
||||
2. Развернуть и проверить notification release на production.
|
||||
3. Добавить webhook и quiet hours, затем начать конфигурационные backup/restore.
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
services:
|
||||
rmm-server:
|
||||
image: "${RMM_SERVER_IMAGE:-ghcr.io/benya9669/openwrt-rmm-server}:${RMM_RELEASE_VERSION:-latest}"
|
||||
pull_policy: "${RMM_IMAGE_PULL_POLICY:-always}"
|
||||
|
||||
tunnel-ssh:
|
||||
image: "${RMM_TUNNEL_IMAGE:-ghcr.io/benya9669/openwrt-rmm-tunnel}:${RMM_RELEASE_VERSION:-latest}"
|
||||
pull_policy: "${RMM_IMAGE_PULL_POLICY:-always}"
|
||||
@@ -69,6 +69,9 @@ RUN case "${OPENWRT_SDK_URL}" in \
|
||||
|
||||
FROM sdk AS builder
|
||||
|
||||
ARG REQUIRE_NATIVE_SIGNATURE=false
|
||||
ARG REPOSITORY_INDEX_REVISION=local
|
||||
|
||||
USER root
|
||||
|
||||
RUN apt-get update \
|
||||
@@ -144,7 +147,11 @@ RUN printf '%s\n' 'CONFIG_PACKAGE_luci-app-rmm-agent=m' >> .config \
|
||||
|
||||
RUN make -j1 package/luci-app-rmm-agent/compile V=s
|
||||
|
||||
RUN mkdir -p /home/builder/artifacts \
|
||||
RUN --mount=type=secret,id=openwrt_usign_secret_b64,uid=1000,required=false \
|
||||
--mount=type=secret,id=openwrt_usign_public_b64,uid=1000,required=false \
|
||||
--mount=type=secret,id=openwrt_apk_secret_b64,uid=1000,required=false \
|
||||
test -n "${REPOSITORY_INDEX_REVISION}" \
|
||||
&& mkdir -p /home/builder/artifacts \
|
||||
&& find bin -type f \
|
||||
\( -name 'luci-app-rmm-agent*.apk' \
|
||||
-o -name 'luci-app-rmm-agent*.ipk' \
|
||||
@@ -153,7 +160,54 @@ RUN mkdir -p /home/builder/artifacts \
|
||||
-exec cp '{}' /home/builder/artifacts/ \; \
|
||||
&& test -n "$(find /home/builder/artifacts -maxdepth 1 -type f \( -name '*.apk' -o -name '*.ipk' \) -print -quit)" \
|
||||
&& cd /home/builder/artifacts \
|
||||
&& find . -maxdepth 1 -type f \( -name '*.apk' -o -name '*.ipk' \) -print \
|
||||
&& if find . -maxdepth 1 -type f -name '*.ipk' -print -quit | grep -q .; then \
|
||||
/home/builder/sdk/scripts/ipkg-make-index.sh . > Packages; \
|
||||
gzip -9nc Packages > Packages.gz; \
|
||||
if [ -s /run/secrets/openwrt_usign_secret_b64 ] \
|
||||
&& [ -s /run/secrets/openwrt_usign_public_b64 ]; then \
|
||||
base64 -d /run/secrets/openwrt_usign_secret_b64 > /tmp/usign.sec; \
|
||||
base64 -d /run/secrets/openwrt_usign_public_b64 > /tmp/usign.pub; \
|
||||
chmod 600 /tmp/usign.sec; \
|
||||
/home/builder/sdk/staging_dir/host/bin/usign -S -m Packages -s /tmp/usign.sec; \
|
||||
/home/builder/sdk/staging_dir/host/bin/usign -V -m Packages -p /tmp/usign.pub; \
|
||||
key_id="$(/home/builder/sdk/staging_dir/host/bin/usign -F -p /tmp/usign.pub)"; \
|
||||
cp /tmp/usign.pub "${key_id}"; \
|
||||
rm -f /tmp/usign.sec /tmp/usign.pub; \
|
||||
elif [ "${REQUIRE_NATIVE_SIGNATURE}" = "true" ]; then \
|
||||
echo 'IPK release signing keys are required' >&2; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
else \
|
||||
if [ -s /run/secrets/openwrt_apk_secret_b64 ]; then \
|
||||
base64 -d /run/secrets/openwrt_apk_secret_b64 > /tmp/apk-signing.pem; \
|
||||
chmod 600 /tmp/apk-signing.pem; \
|
||||
/home/builder/sdk/staging_dir/host/bin/openssl ec \
|
||||
-in /tmp/apk-signing.pem -pubout -out rmm-openwrt.pem; \
|
||||
/home/builder/sdk/staging_dir/host/bin/apk mkndx \
|
||||
--root /home/builder/sdk \
|
||||
--keys-dir /home/builder/artifacts \
|
||||
--allow-untrusted \
|
||||
--sign /tmp/apk-signing.pem \
|
||||
--output packages.adb \
|
||||
./*.apk; \
|
||||
rm -f /tmp/apk-signing.pem; \
|
||||
elif [ "${REQUIRE_NATIVE_SIGNATURE}" = "true" ]; then \
|
||||
echo 'APK release signing key is required' >&2; \
|
||||
exit 1; \
|
||||
else \
|
||||
/home/builder/sdk/staging_dir/host/bin/apk mkndx \
|
||||
--root /home/builder/sdk \
|
||||
--keys-dir /home/builder/artifacts \
|
||||
--allow-untrusted \
|
||||
--output packages.adb \
|
||||
./*.apk; \
|
||||
fi; \
|
||||
fi \
|
||||
&& find . -maxdepth 1 -type f \( \
|
||||
-name '*.apk' -o -name '*.ipk' \
|
||||
-o -name 'Packages' -o -name 'Packages.gz' -o -name 'Packages.sig' \
|
||||
-o -name 'packages.adb' -o -name 'rmm-openwrt.pem' \
|
||||
-o -name '????????????????' \) -print \
|
||||
| LC_ALL=C sort \
|
||||
| xargs sha256sum > SHA256SUMS
|
||||
|
||||
|
||||
@@ -58,6 +58,23 @@ by retention maintenance.
|
||||
|
||||
## 2. Start The Stack
|
||||
|
||||
For a production deployment, pin the server release in `.env` and use the release
|
||||
overlay. This pulls the server and tunnel images published by the same `server-v*` tag:
|
||||
|
||||
```dotenv
|
||||
RMM_RELEASE_VERSION=0.8.0
|
||||
```
|
||||
|
||||
```powershell
|
||||
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
|
||||
```
|
||||
|
||||
Keep the exact version instead of `latest` so an upgrade is deliberate and reversible.
|
||||
The base Compose file remains buildable from source for development and emergency
|
||||
recovery:
|
||||
|
||||
```powershell
|
||||
docker compose up -d --build
|
||||
docker compose ps
|
||||
|
||||
+3
-1
@@ -147,7 +147,9 @@ The workflow resolves each SDK filename and SHA256 from the official OpenWrt mir
|
||||
falling back to the OpenWrt archive for end-of-life branches. Releases through `24.10`
|
||||
produce `.ipk`; `25.12` produces `.apk`. Manual runs retain each target as a workflow
|
||||
artifact for 30 days. An `agent-v*` tag also creates a GitHub Release containing all packages and
|
||||
a combined `SHA256SUMS`.
|
||||
a combined `SHA256SUMS`, a Sigstore signature bundle and native signed package feeds.
|
||||
See [package-repository.md](package-repository.md) for key provisioning and the commands
|
||||
used to connect `opkg` or `apk` to the repository.
|
||||
|
||||
This matrix represents common CPU families, not every OpenWrt target. Add an explicit
|
||||
matrix row for another target/subtarget and select the matching Go architecture rather
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
# Signed OpenWrt package repository
|
||||
|
||||
Agent release tags publish a repository for every supported OpenWrt release and target.
|
||||
The default public base URL is:
|
||||
|
||||
```text
|
||||
https://benya9669.github.io/openwrt-rmm/feeds/stable/openwrt
|
||||
```
|
||||
|
||||
Before the first release, open **Repository settings → Pages** and select **GitHub
|
||||
Actions** as the deployment source. A custom domain such as `packages.daemonlord.ru`
|
||||
can be attached later in the same Pages settings; keep the GitHub Pages URL available
|
||||
until DNS and TLS for the custom domain have been verified.
|
||||
|
||||
## Signing keys
|
||||
|
||||
Keep private keys offline and out of the repository. Generate them on a trusted Linux or
|
||||
WSL host with `usign` and OpenSSL installed:
|
||||
|
||||
```sh
|
||||
umask 077
|
||||
mkdir -p release-keys
|
||||
usign -G \
|
||||
-s release-keys/openwrt-usign.sec \
|
||||
-p release-keys/openwrt-usign.pub \
|
||||
-c "OpenWrt RMM package repository"
|
||||
openssl ecparam -name prime256v1 -genkey -noout \
|
||||
-out release-keys/openwrt-apk.pem
|
||||
openssl ec -in release-keys/openwrt-apk.pem -pubout \
|
||||
-out release-keys/openwrt-apk.pub.pem
|
||||
```
|
||||
|
||||
Back up `openwrt-usign.sec` and `openwrt-apk.pem` in an encrypted offline location.
|
||||
The files under `release-keys/` are ignored by Git.
|
||||
|
||||
Encode the keys without printing private material into CI logs:
|
||||
|
||||
```sh
|
||||
base64 -w0 release-keys/openwrt-usign.sec > release-keys/openwrt-usign.sec.b64
|
||||
base64 -w0 release-keys/openwrt-usign.pub > release-keys/openwrt-usign.pub.b64
|
||||
base64 -w0 release-keys/openwrt-apk.pem > release-keys/openwrt-apk.pem.b64
|
||||
```
|
||||
|
||||
Create these GitHub Actions repository secrets from the corresponding `.b64` files:
|
||||
|
||||
| Secret | Source file |
|
||||
| --- | --- |
|
||||
| `OPENWRT_USIGN_SECRET_B64` | `openwrt-usign.sec.b64` |
|
||||
| `OPENWRT_USIGN_PUBLIC_B64` | `openwrt-usign.pub.b64` |
|
||||
| `OPENWRT_APK_SECRET_B64` | `openwrt-apk.pem.b64` |
|
||||
|
||||
An `agent-v*` release fails closed when the key required by an OpenWrt generation is
|
||||
missing. Manual workflow runs may still create unsigned test artifacts. BuildKit secret
|
||||
mounts expose private keys only to the repository-index build step; private keys are not
|
||||
copied into images, artifacts or build cache.
|
||||
|
||||
## OpenWrt 24.10 and older: IPK/opkg
|
||||
|
||||
Choose the directory matching the firmware release and target. For example MT7621 on
|
||||
OpenWrt 24.10:
|
||||
|
||||
```sh
|
||||
feed='https://benya9669.github.io/openwrt-rmm/feeds/stable/openwrt/24.10.7/ramips-mt7621'
|
||||
key_base='https://benya9669.github.io/openwrt-rmm/keys/usign'
|
||||
key_id='<16-character-key-id>'
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
The workflow creates `Packages`, `Packages.gz` and `Packages.sig`. `opkg` verifies the
|
||||
signature of the repository metadata and the package hashes contained in that metadata.
|
||||
The key ID is the filename published under `/keys/usign/`.
|
||||
|
||||
## OpenWrt 25.12 and newer: APK
|
||||
|
||||
For MT7621 on OpenWrt 25.12:
|
||||
|
||||
```sh
|
||||
base='https://benya9669.github.io/openwrt-rmm'
|
||||
repo="${base}/feeds/stable/openwrt/25.12.4/ramips-mt7621/packages.adb"
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
APK verifies the signed `packages.adb` index. Installation should not require
|
||||
`--allow-untrusted`; needing that flag means the repository key or signature chain is
|
||||
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.*$' \
|
||||
--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.
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$#" -ne 3 ]; then
|
||||
echo "usage: $0 <downloaded-artifacts-dir> <output-dir> <agent-version>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
source_dir="$1"
|
||||
output_dir="$2"
|
||||
agent_version="$3"
|
||||
|
||||
if [ ! -d "$source_dir" ]; then
|
||||
echo "artifact directory does not exist: $source_dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$agent_version" in
|
||||
''|*[!0-9A-Za-z._-]*)
|
||||
echo "invalid agent version: $agent_version" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -e "$output_dir" ] && [ -n "$(find "$output_dir" -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null)" ]; then
|
||||
echo "output directory must be empty: $output_dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$output_dir/feeds/$agent_version/openwrt" "$output_dir/keys/usign" "$output_dir/keys/apk"
|
||||
|
||||
artifact_count=0
|
||||
for artifact_dir in "$source_dir"/openwrt-*; do
|
||||
[ -d "$artifact_dir" ] || continue
|
||||
artifact_name="$(basename "$artifact_dir")"
|
||||
remainder="${artifact_name#openwrt-}"
|
||||
openwrt_release="${remainder%%-*}"
|
||||
target_label="${remainder#"$openwrt_release"-}"
|
||||
|
||||
if [ -z "$openwrt_release" ] || [ -z "$target_label" ] || [ "$target_label" = "$remainder" ]; then
|
||||
echo "cannot parse artifact name: $artifact_name" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
destination="$output_dir/feeds/$agent_version/openwrt/$openwrt_release/$target_label"
|
||||
mkdir -p "$destination"
|
||||
cp "$artifact_dir"/* "$destination/"
|
||||
|
||||
while IFS= read -r public_key; do
|
||||
cp "$public_key" "$output_dir/keys/usign/$(basename "$public_key")"
|
||||
done < <(find "$artifact_dir" -maxdepth 1 -type f -regextype posix-extended \
|
||||
-regex '.*/[0-9a-f]{16}' -print)
|
||||
|
||||
if [ -f "$artifact_dir/rmm-openwrt.pem" ]; then
|
||||
cp "$artifact_dir/rmm-openwrt.pem" "$output_dir/keys/apk/rmm-openwrt.pem"
|
||||
fi
|
||||
artifact_count=$((artifact_count + 1))
|
||||
done
|
||||
|
||||
if [ "$artifact_count" -eq 0 ]; then
|
||||
echo "no openwrt-* artifact directories found in $source_dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$output_dir/feeds/stable"
|
||||
cp -a "$output_dir/feeds/$agent_version/." "$output_dir/feeds/stable/"
|
||||
touch "$output_dir/.nojekyll"
|
||||
|
||||
cat > "$output_dir/index.html" <<EOF
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>OpenWrt RMM package repository</title>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>OpenWrt RMM package repository</h1>
|
||||
<p>Текущая стабильная версия агента: <strong>${agent_version}</strong>.</p>
|
||||
<p>Инструкции подключения находятся в документации проекта.</p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
Reference in New Issue
Block a user