build: add signed package repository releases

This commit is contained in:
benya
2026-07-23 01:36:50 +03:00
parent a7ce37f1f8
commit 8da039f05c
13 changed files with 440 additions and 17 deletions
+7
View File
@@ -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=
+90 -4
View File
@@ -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
+37
View File
@@ -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')
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+8
View File
@@ -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}"
+56 -2
View File
@@ -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
+17
View File
@@ -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
View File
@@ -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
+110
View File
@@ -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.
+84
View File
@@ -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