Кукбук · 2 из 3

Сборка и релиз

Превратить работающий пакет в подписанные артефакты, которые любой может проверить. Ключей тут два, и работу они делают разную — это стоит прочитать до того, как копировать workflow.

Сделать два ключа, один раз

owfeed keygen -o author.pem            # EC prime256v1 — подписывает пакеты
owfeed keygen --usign -o release.key   # usign/ed25519 — подписывает манифест релиза

gh secret set OWFEED_AUTHOR_KEY < author.pem
gh secret set OWFEED_RELEASE_KEY < release.key

git add author.pub.pem release.pub && git commit -m "publish the public halves"
Коммитьте публичные половины, приватные — никогда. Утёкший подписывающий ключ не отозвать: у apk нет ни CRL, ни срока жизни, ни способа сказать, что ключ мёртв. Генерируйте вне рабочего дерева git — owfeed keygen откажется писать внутрь него без --force. Запишите id ключей, которые он печатает: фид их спросит.

Почему два

КлючПодписываетКто проверяет
EC (author.pem)внутри каждого .apkкто угодно и когда угодно, по вашей публичной половине
usign (release.key)манифест релизафид, один раз, когда забирает релиз

Подпись usign проверяется при приёмке — и на этом всё, за пределы CI фида она не выходит. Подпись EC едет внутри пакета до самого роутера, поэтому тот, кто фиду не доверяет, всё равно может убедиться: собрали это вы. Это единственное утверждение о пакете, которое переживает раздачу через фид, — потому фиды её и требуют.

Собрать обе линии релизов

owlab build --release 25.12.5    # apk, для 25.12 и новее
owlab build --release 24.10.8    # ipk, для 24.10 и старее

Обе попадают в dist/<arch>/. Пакет, не зависящий от архитектуры, даёт два каталога — и это правильно, а не баг: apk требует noarch и отвергает all, opkg про noarch никогда не слышал.

dist/
├── noarch/my-app-1.0.0-r1.apk
└── all/my-app_1.0.0-r1_all.ipk

Выпуская только под 25.12, вы бросаете всех, кто не обновился, — а на релизе роутеры сидят годами.

Соберите пакет перед релизом, даже если sync работал. luci.mk минифицирует JS и CSS, owlab sync — нет. Код, который работает неминифицированным и ломается после минификации, не виден, пока кто-нибудь не соберёт пакет.

Сначала подпись, потом манифест — именно в таком порядке

owfeed sign --key env:OWFEED_AUTHOR_KEY
owfeed release --repo "$GITHUB_REPOSITORY" --tag "$GITHUB_REF_NAME"

Порядок здесь не вкусовщина. owfeed release записывает размер и sha256 каждого пакета, а подпись дописывает байты в файл. Поменяете местами — манифест будет описывать файлы, которых больше нет.

owfeed release пишет manifest.txt: что входит в этот релиз, размер и хеш каждого файла, из какого репозитория и тега он приехал — и подписывает всё это вашим ключом usign. Репозиторий там не для красоты: подпись доказывает, кто это написал, но никогда — о чём это. Без той строки манифест, взятый из другого вашего релиза, прекрасно прошёл бы проверку как этот.

GitHub Actions

.github/workflows/release.yml

name: release
on:
  push: { tags: ["v*"] }
permissions: { contents: read }
jobs:
  release:
    runs-on: ubuntu-latest
    permissions: { contents: write }
    steps:
      - uses: actions/checkout@v5
      - uses: owfeed/owlab/setup@v0.5.1
      - uses: owfeed/owfeed/setup@v0.5.0

      - run: owlab build --release 25.12.5
      - run: owlab build --release 24.10.8

      - name: Sign the packages, then write the manifest
        env:
          OWFEED_AUTHOR_KEY:  ${{ secrets.OWFEED_AUTHOR_KEY }}
          OWFEED_RELEASE_KEY: ${{ secrets.OWFEED_RELEASE_KEY }}
        run: |
          owfeed sign --key env:OWFEED_AUTHOR_KEY
          owfeed release

      - run: gh release create "${{ github.ref_name }}" --generate-notes
             dist/*/*.apk dist/*/*.ipk dist/manifest.txt*
        env: { GH_TOKEN: ${{ github.token }} }

owfeed release сам читает репозиторий и тег из GITHUB_REPOSITORY и GITHUB_REF_NAME на GitHub и из CI_PROJECT_PATH и CI_COMMIT_TAG на GitLab, так что флаги нужны только чтобы их перебить.

Держите шаги с подписью в джобе, до которой не дотянется пул-реквест. PR из форка запускает вашу сборку, и секрет, доступный оттуда, — это опубликованный секрет. Ограничьте джобу тегом, как выше, или вынесите её отдельно с environment:.

GitLab CI

release:
  rules:
    - if: $CI_COMMIT_TAG
  script:
    - wget -qO- https://github.com/owfeed/owlab/releases/download/v0.5.1/owlab_0.5.1_linux_amd64.tar.gz | tar xz
    - wget -qO owfeed https://github.com/owfeed/owfeed/releases/latest/download/owfeed-linux-amd64
    - chmod +x owfeed && export PATH="$PWD:$PATH"
    - owlab build --release 25.12.5
    - owlab build --release 24.10.8
    - owfeed sign --key env:OWFEED_AUTHOR_KEY
    - owfeed release

Проверить раньше, чем это сделают другие

owfeed doctor --require-origin

Нумерованные находки, каждая говорит, чем это грозит и что делать. Те, что ловят большинство первых попыток:

НаходкаЧто это значит
arch: allapk считает такой пакет неустанавливаемым. Пишите LUCI_PKGARCH:=all и дайте сборке перевести это самой — так делает и собственная упаковка OpenWrt.
OWF207Файл /etc/config/* кладётся, но не объявлен в conffiles:, поэтому sysupgrade молча заменит настройки пользователя вашими дефолтами при каждом обновлении прошивки.
Версия отклоненаПосле ~ apk ждёт хеш коммита, то есть только hex. Пишите 1.0_beta1, а не 1.0~beta.
Переводы не видныLuCI читает скомпилированные .lmo и игнорирует .po. Укажите каталоги в i18n.from:.