Протокол манифеста обновлений
Открытый протокол JSON, который приложения Wails используют для обнаружения и проверки собственных обновлений; манифест можно размещать на любом хостинге статических файлов или динамическом сервере обновлений.
Протокол Wails Update Manifest — это небольшой открытый контракт в формате JSON между приложением Wails и источником обновлений. Обновления Wails может предоставлять любой ресурс, способный передавать файл JSON по HTTPS: бакет S3, GitHub Pages, CDN или динамический сервер обновлений, который предоставляет выпуски только при наличии лицензии.
Клиентская часть поставляется вместе с фреймворком в виде провайдера endpoint (github.com/wailsapp/wails/v3/pkg/updater/providers/endpoint). На этой странице приведено справочное описание формата обмена данными для разработчиков серверной части.
Цели проектирования
- Поддержка статического хостинга. Для полноценной реализации достаточно одного файла манифеста на каждый канал со списком артефактов для всех платформ. Серверный код не требуется.
- Поддержка динамического сервера. При каждой проверке клиент отправляет
platform,arch,versionиchannel, поэтому сервер может вернуть ровно один артефакт, применить правила лицензирования или вернуть204 No Content, если у вызывающей стороны уже установлена актуальная версия. - Проверка прежде всего. Манифест содержит контрольные суммы и подписи для каждого артефакта, а средство обновления Wails проверяет их с помощью открытого ключа, закреплённого в двоичном файле приложения во время сборки. Источник обновлений никогда не выбирает собственный корень доверия.
Запрос
Клиент отправляет GET на настроенный URL-адрес манифеста с Accept: application/json и всеми заголовками, настроенными приложением (например, Authorization: License <key>).
URL-адрес может содержать заполнители, которые клиент заменяет при каждой проверке:
| Заполнитель | Заменяется на |
|---|---|
{{platform}} |
Текущая ОС в виде значения Go GOOS (darwin, windows, linux) |
{{arch}} |
Текущая архитектура в виде значения Go GOARCH (amd64, arm64, …) |
{{version}} |
Текущая установленная версия |
{{channel}} |
Настроенный канал выпуска, если он задан |
Любое из четырёх значений, не подставленное вместо заполнителя, добавляется как параметр запроса с тем же именем (channel — только если он настроен). Поэтому обе следующие конфигурации допустимы и эквивалентны:
# Dynamic server: reads query parameters
https://updates.example.com/check
-> GET /check?platform=darwin&arch=arm64&version=1.0.0&channel=stable
# Static host: one manifest per platform/arch/channel path
https://cdn.example.com/updates/{{platform}}/{{arch}}/{{channel}}.json
-> GET /updates/darwin/arm64/stable.json?version=1.0.0Статические хостинги просто игнорируют полученные параметры запроса.
Ответ
| Статус | Значение |
|---|---|
200 OK |
Далее следует манифест. Клиент определяет, является ли указанная версия обновлением. |
204 No Content |
Сервер сравнил версии, и у вызывающей стороны установлена актуальная версия. |
404 Not Found |
Ничего не опубликовано (обрабатывается так же, как наличие актуальной версии). |
| Любой другой | Ошибка. Средство обновления переходит к следующему настроенному провайдеру. |
Тело 200 представляет собой документ манифеста:
{
"schemaVersion": 1,
"version": "2.1.0",
"channel": "stable",
"name": "Summer Release",
"notes": "## What's new\n\n- Faster startup\n- New themes",
"publishedAt": "2026-07-03T10:00:00Z",
"artifacts": [
{
"url": "MyApp-2.1.0-darwin-arm64.zip",
"platform": "darwin",
"arch": "arm64",
"filetype": "zip",
"size": 8388608,
"digestAlgo": "sha512",
"digest": "base64-encoded digest bytes",
"signatureAlgo": "ed25519ph",
"signature": "base64-encoded signature bytes"
},
{
"url": "MyApp-2.1.0-windows-amd64.zip",
"platform": "windows",
"arch": "amd64",
"filetype": "zip",
"size": 9437184,
"digestAlgo": "sha512",
"digest": "...",
"signatureAlgo": "ed25519ph",
"signature": "..."
}
]
}Поля верхнего уровня
| Поле | Тип | Обязательное | Примечания |
|---|---|---|---|
schemaVersion |
int | нет | Версия протокола. Если поле отсутствует, подразумевается 1. Клиенты отклоняют значения, соответствующие более новым, неизвестным им версиям. |
version |
string | да | Версия, соответствующая спецификации SemVer 2.0.0, с начальным v или без него. |
channel |
string | нет | Информационное поле. Клиент, настроенный на другой канал, считает, что обновление отсутствует. |
name |
string | нет | Понятное пользователю название выпуска, отображаемое в окне обновления. |
notes |
string | нет | Примечания к выпуску в формате Markdown, отображаемые в окне обновления. |
publishedAt |
string | нет | Метка времени в формате RFC 3339. |
artifacts |
array | да | По одной записи для каждого доступного для скачивания артефакта. Порядок отражает предпочтения издателя. |
metadata |
object | нет | Данные в виде произвольных пар «ключ — значение», передаваемые приложению без изменений. |
Клиенты игнорируют неизвестные поля, поэтому серверы могут добавлять собственные поля, не нарушая работу клиентов. Специфичные для сервера дополнения следует помещать в metadata.
Поля артефакта
| Поле | Тип | Обязательное | Примечания |
|---|---|---|---|
url |
string | да | Абсолютный или относительный по отношению к URL манифеста. Только http(s). |
platform |
string | нет | Значение Go GOOS. Поддерживаются распространённые псевдонимы (macos, win, …). Пустое значение соответствует любой платформе. |
arch |
string | нет | Значение Go GOARCH. Поддерживаются распространённые псевдонимы (x86_64, aarch64, …). Пустое значение соответствует любой архитектуре. |
filename |
string | нет | По умолчанию используется последний сегмент пути из url. |
filetype |
string | нет | По умолчанию используется расширение имени файла. |
size |
int | нет | Размер в байтах, используемый для отображения хода загрузки. |
digestAlgo / digest |
string / base64 | нет | sha256 или sha512. |
signatureAlgo / signature |
string / base64 | нет | ed25519, ed25519ph или ecdsa-p256. При наличии signature поле signatureAlgo обязательно. О том, что подписывает каждый алгоритм, см. в руководстве по средству обновления. |
Клиент выбирает первый артефакт, у которого platform и arch соответствуют текущей системе. Значения Base64 принимаются как с заполнением, так и без него.
Сравнение версий
Решение о том, является ли манифест обновлением, всегда принимается на стороне клиента согласно порядку приоритетов SemVer 2.0.0: значение version в манифесте должно быть строго новее установленной версии. Благодаря этому статический хостинг работает корректно без дополнительных усилий: манифест всегда описывает последний выпуск, а клиенты с актуальной версией просто ничего не делают. При этом динамические серверы по-прежнему могут использовать 204 для экономии трафика.
Проверка и доверие
Контрольные суммы и подписи передаются в манифесте, но корень доверия — нет: подписи проверяются по открытому ключу, который приложение закрепило через updater.Config.PublicKey во время сборки. Скомпрометированный или подменённый источник обновлений не может предоставить собственный ключ. Если артефакт содержит подпись, но в приложении не закреплён ключ, проверка завершается отказом. То же происходит, если для подписи не объявлено значение signatureAlgo или подпись не удаётся декодировать: клиенты никогда не переходят без уведомления к проверке только по дайджесту.
Артефакты только с дайджестом устанавливаются после проверки дайджеста. Это защищает от повреждения данных, но защита от подмены зависит от TLS и от того, что сам хост не скомпрометирован. Добавляйте подписи ко всему, что критично для безопасности.
Чтобы подписать артефакт по схеме ed25519ph, используемой фреймворком, достаточно нескольких строк кода Go:
digest := sha512.Sum512(artifactBytes)
sig, _ := privateKey.Sign(nil, digest[:], &ed25519.Options{Hash: crypto.SHA512})
manifest.Artifacts[i].DigestAlgo = "sha512"
manifest.Artifacts[i].Digest = base64.StdEncoding.EncodeToString(digest[:])
manifest.Artifacts[i].SignatureAlgo = "ed25519ph"
manifest.Artifacts[i].Signature = base64.StdEncoding.EncodeToString(sig)На практике писать этот код почти никогда не приходится: CLI делает это за вас.
Публикация с помощью CLI wails3
Группа команд wails3 updater охватывает весь процесс публикации. Для выпуска нужны три команды:
# Once per application: create the signing keypair.
wails3 updater genkey
# updater.key keep secret (CI secret store), signs every release
# updater.key.pub embed in the app and pass as updater.Config.PublicKey
# Per release: digest, sign and describe every artifact in one manifest.
wails3 updater manifest -version 2.1.0 -channel stable \
-key updater.key -notes-file notes.md \
-url-prefix "https://cdn.example.com/myapp/2.1.0" \
bin/updates/
# Before uploading: re-verify the files exactly as a shipped app would.
wails3 updater verify -manifest manifest.json -publickey updater.key.pub
manifest принимает файлы или каталоги (материалы ключей, .json, вспомогательные файлы контрольных сумм и примечаний пропускаются автоматически), потоково обрабатывает каждый артефакт с помощью SHA-512, подписывает дайджест алгоритмом Ed25519ph, если указан -key, и определяет platform и arch по стандартным именам файлов, таким как MyApp-2.1.0-darwin-arm64.zip. Поддерживаются распространённые псевдонимы, например macOS, win64, x86_64 и aarch64. Если определить значение не удаётся, выводится предупреждение, после чего артефакт считается подходящим для любой платформы. Не указывайте -url-prefix, чтобы сформировать относительные URL и загрузить манифест рядом с артефактами.
При любом несоответствии verify завершается с ненулевым кодом, поэтому эту команду удобно использовать как проверку CI между сборкой и публикацией. Для серверов, которые самостоятельно формируют манифесты, wails3 updater sign -key updater.key <files...> выводит поля digest/signature каждого файла в формате JSON, готовом для включения в ваш документ.
Аутентификация
Аутентификацией занимается сервер; протокол лишь передаёт заголовки. Клиент повторно отправляет настроенные заголовки с каждым запросом манифеста. При загрузке артефактов заголовок Authorization отправляется только в том случае, если URL артефакта находится на том же хосте, что и манифест, и протокол не понижается с https до http. При любом перенаправлении на другой источник или с понижением протокола этот заголовок удаляется, поэтому учётные данные никогда не передаются в CDN или объектное хранилище и никогда не отправляются в открытом виде.
Пример с доступом по лицензии, естественным образом сочетающийся с размещёнными службами лицензирования:
ep, _ := endpoint.New(endpoint.Config{
URL: "https://updates.example.com/check",
Headers: map[string]string{"Authorization": "License " + licenseKey},
})Настройка клиента
Полное справочное описание endpoint.Config и сведения о включении провайдера в цепочки резервирования наряду с провайдерами GitHub, keygen.sh и AppCast см. в руководстве по средству обновления.