Маніфести

From docs/uk/MANIFESTS.md at 4e43843 View on GitHub

Маніфест — це конфігурація одного пакета: звідки він береться, який асет релізу брати й що виставляти назовні після розпакування.

Він потрібен рідко. ketch виводить маніфест з одного лише owner/repo, і для більшості проєктів цього досить. Пишіть маніфест, коли виведення помиляється: асет із незвичною назвою, бінарник, на який треба поставити посилання під іншою назвою, бандл .app, короткий аліас, який варто запам'ятати.

Та сама схема використовується в трьох місцях:

ДеФайлДля кого
Ваша машина~/.ketch/manifests/<name>.tomlлише для вас
Реєстр пакетів<package>/ketch.tomlдля всіх — див. REGISTRY.md
Вбудовано в ketchcrates/ketch-core/src/builtin.tomlдля всіх, офлайн

Пошук іде в цьому порядку, тому ваш власний маніфест завжди перемагає.

Як написати маніфест

ketch config create напише його за вас, тож схема нижче — довідник, який можна читати, а не форма, яку треба завчати. Команда питає, що має бути в кожному полі, — джерело, назва, записи bin, шаблони асетів і решта, — показуючи типове значення, щоб порожня відповідь його зберігала, а потім показує зібраний файл перед записом.

ketch config create    # питає, показує, записує ./ketch.toml

--file записує в інше місце, --force замінює вже наявний файл, а --yes записує без фінального підтвердження. Відповіді можна натомість передати через stdin — по одній на рядок, порожній рядок означає типове значення, — тож опитувальник працює як зі скрипта, так і в терміналі. Далі, щоб запропонувати файл усім, потрібен пул-реквест до реєстру — див. REGISTRY.md.

Якщо пакет уже є у winget, Homebrew або Arch Linux, ketch import записує маніфест із цього опису, лишаючи лише поля нижче, які потрібні ketch — див. ketch import.

ketch import brew codex --dry-run   # надрукувати ~/.ketch/manifests/codex.toml

Підтримка редактора

manifest.schema.json — це JSON Schema файлу ketch.toml, згенерована з типів, якими ketch його читає. Сервер мови TOML, що розуміє директиву #:schema — Taplo, на якому працює розширення Even Better TOML, або Tombi — тоді доповнює ключі, показує документацію нижче під час набору і позначає одрук у ключі раніше, ніж це зробить ketch. Поставте директиву першим рядком:

#:schema https://raw.githubusercontent.com/pyrlyn/ketch/main/docs/manifest.schema.json
source = "github:BurntSushi/ripgrep"

Вона описує один пакет: ketch.toml реєстру чи проєкту або файл одного пакета в ~/.ketch/manifests/. Файл із масивом [[package]], як builtin.toml, — це список таких пакетів, а не ця форма. name у схемі необов'язкове, бо його дає тека реєстру; скрізь іще ketch і далі його вимагає.

Схема каже те, що схема може: невідомі ключі, дозволені значення kind, verifier, mode і видів extra_paths, синтаксис source, імена, придатні як назва файлу, аліаси provides без пробілів, strip_prefix не більше 8, запис bin із name або path і непорожні хуки. Решта з Що перевіряє ketch — шляхи, які мають лишатися всередині вмісту, як класифікується запис extra_paths, які ключі trust належать якому перевіряльнику і чи розбирається ключ — як і раніше робить ketch під час завантаження. Як і в ketch, записи bin і таблиця asset не відхиляють ключі, яких вони не знають.

Найменший

source = "github:BurntSushi/ripgrep"

Усе інше має типові значення. У теці пакета реєстру назва береться з теки; в інших місцях name обов'язкова. ketch config create також записує запис bin з назвою команди, про яку завжди питає.

Повний

name = "ripgrep"
source = "github:BurntSushi/ripgrep"
description = "Recursively search directories for a regex pattern"
homepage = "https://github.com/BurntSushi/ripgrep"
kind = "binary"
provides = ["rg"]
prerelease = false
strip_prefix = 1
notes = "Shell completions are under complete/ in the payload."

bin = [{ path = "*/rg", name = "rg" }]
extra_paths = ["complete/rg.bash", "doc/rg.1"]

[hooks]
after_install = "./rg --version"
after_update = "rm -rf \"$HOME/.cache/rg\""

[asset]
include = ["*-apple-darwin.tar.gz"]
exclude = ["*-musl-*"]

[asset.target]
"macos-aarch64" = "*-aarch64-apple-darwin.tar.gz"
"macos-x86_64" = "*-x86_64-apple-darwin.tar.gz"
"linux-x86_64" = "*-x86_64-unknown-linux-gnu.tar.gz"
"linux-aarch64" = "*-aarch64-unknown-linux-gnu.tar.gz"
"windows-x86_64" = "*-x86_64-pc-windows-msvc.zip"
"windows-aarch64" = "*-aarch64-pc-windows-msvc.zip"

Поля

КлючТипТиповоЩо робить
nameрядокназва теки в пакеті реєструНазва встановлення. Стає каталогом у сховищі й ключем у state.json.
sourceрядок— обов'язковоowner/repo або scheme:id для плагіна джерела.
descriptionрядокнемаєОдин рядок, який показують ketch info і ketch search.
homepageрядокнемаєURL для людей.
kindauto | binary | appautoЧим є вміст. Див. нижче.
providesсписок рядківпорожньоІнші назви, на які відгукується цей пакет. ketch install rg працює, бо ripgrep надає rg.
prereleaseboolfalseВраховувати пререлізи під час розв'язання latest.
strip_prefixціле 0–8зняти один каталог-обгорткуСкільки початкових компонентів шляху відкидати під час розпакування.
notesрядокнемаєДрукується після успішного встановлення.
binсписок таблицьвиявленняНа які виконувані файли ставити посилання й під якими назвами.
extra_pathsсписок рядків або таблицьпорожньоMan-сторінки й автодоповнення, на які ставляться посилання в користувацьких каталогах.
assetтаблицявибір платформиЗвужує вибір асета релізу.
trustтаблицянемаєЧий підпис має бути в релізу. Див. нижче.
hooksтаблицянемаєКоманди, що виконуються до й після встановлення, оновлення та видалення. Див. нижче.

Невідомі ключі — це помилка, а не попередження. Ключ з одруківкою, який мовчки ігнорується, дає пакет, що встановлює не те й нічого про це не каже.

kind

  • auto — подивитися на вміст. Бандл .app робить пакет застосунком; усе інше вважається бінарниками.
  • binary — ставити посилання на виконувані файли в PATH і ніколи не розміщувати .app.
  • app — розмістити бандл .app і не розкидати його виконувані файли по PATH.

bin

Кожному запису потрібен path, name або обидва.

bin = [
  { name = "rg" },                    # знайти у вмісті файл з назвою `rg`
  { path = "bin/tool" },              # поставити посилання на цей точний шлях як `tool`
  { path = "*/tool-*", name = "tool" }, # знайти за глобом, поставити посилання як `tool`
]

path — це глоб (* і ?), що зіставляється зі шляхом відносно кореня вмісту. name — назва файлу символьного посилання в ~/.ketch/bin.

Глоб, якому пасує кілька файлів, ставить посилання на той, чий stem дорівнює name (без урахування регістру, .exe ігнорується): rtok* з name = "rtok" бере rtok, а не rtok-hook. Коли пасує кілька файлів і жоден не названий як name — або в запису немає name — встановлення відмовляється і перелічує кандидатів, бо порядок каталогу різниться між операційними системами і поставив би посилання на різний файл. Задайте name або звузьте path до одного файлу.

Якщо bin немає взагалі, ketch сам виявляє виконувані файли: він дивиться вглиб до чотирьох рівнів, ігнорує каталоги документації та нутрощі бандлів і віддає перевагу каталогу bin/, якщо він є у вмісті. Єдиний виконуваний файл, назва якого явно несе метадані збірки, — jq-macos-arm64, — отримує посилання під назвою пакета.

Виявлення ставить посилання на все знайдене, доки реліз не містить кілька виконуваних файлів із назвою пакета — скажімо, rtok поруч із rtok-hook, де один — це команда, а другий — допоміжна програма. Назва збігається з назвою пакета, якщо це назва пакета або назва пакета, за якою йде -, _ чи . (gofmt не збігається з go). Тоді ketch ставить посилання рівно на один із них, обраний однаково на всіх ОС, а решту з цієї родини пропускає; виконувані файли з іншими назвами — other-tool поруч із rtok і rtok-hook — отримують посилання як завжди. Вибір такий:

  1. той, що названий у --bin <name> для ketch install або ketch upgrade, без урахування регістру та .exe; назва, що не збігається з жодним бінарником релізу, — помилка;
  2. інакше той, чия назва збігається з назвою пакета, — rtok, але ніколи не rtok-hook, — без урахування регістру та .exe;
  3. інакше той, що був обраний для цього пакета раніше: state.json пам'ятає його між оновленнями, перевстановленнями, ketch link і ketch rollback, а ketch sync також читає його з ключа bin у ketch.lock (спершу стан, потім lock-файл);
  4. інакше той, який ви оберете з нумерованого списку, коли ketch працює в терміналі й --yes не передано;
  5. інакше нічого: встановлення завершується помилкою зі списком кандидатів і записом bin, який розв'язав би питання.

Якщо маніфест, що відповів, — ваш власний (~/.ketch/manifests/<name>.toml), вибір записується в нього як bin = [{ name = "..." }, ...] — обраний бінарник і всі інші, що отримали посилання, — а решта файлу лишається точно такою, якою була, і відтепер файл сам називає свої бінарники. Для маніфесту з реєстру, вбудованого або виведеного вибір зберігається в state.json (а ketch lock копіює його в ketch.lock), і маніфест далі оновлюється разом із реєстром.

ketch config create завжди питає назву бінарника, тому файл пакета, який він записує, має запис bin. Маніфест без нього однаково завантажується.

extra_paths

Кожен запис — шлях відносно розпакованого вмісту, у вигляді рядка або таблиці.

extra_paths = [
  "complete/rg.bash",
  "doc/rg.1",
  { path = "misc/custom", kind = "completion", shell = "bash" },
]

Рядок класифікується за цими правилами й відхиляється, якщо підходять обидва або жодне:

  • Автодоповнення — один із компонентів шляху дорівнює complete, completions, completion, bash-completion або site-functions, а назва файлу — *.bash, *.zsh, *.fish, *.ps1, *.elv, zsh-івське _name без інших крапок або назва без розширення всередині bash-completion / site-functions.
  • Man-сторінка — один із компонентів шляху дорівнює man, manN (N — розділ man), doc або docs, а назва файлу — name.N або name.N.gz, де N — від 1 до 9, після чого можуть іти малі літери (1, 8, 1p).

complete/rg.1 підходить під обидві родини й відхиляється. Файл у корені вмісту, наприклад rg.1, відхиляється. Замість вгадування задайте kind.

Таблиця має задавати kind (man або completion). shell обов'язковий для автодоповнення, назва файлу якого не називає оболонку. section обов'язковий для man-сторінки, назва файлу якої не *.N. Шляхи й далі обмежені вмістом.

ketch ставить посилання на класифіковані файли в користувацький корінь man і каталоги автодоповнень, про які повідомляє ketch doctor. Місця призначення записуються в state.json, як і бінарники, тож видалення й повторне розставлення посилань використовують той самий доказ володіння.

asset

[asset]
include = ["*-apple-darwin.tar.gz"]   # має збігтися принаймні один
exclude = ["*-musl-*"]                # не має збігтися жоден

[asset.target]
"macos-aarch64" = "*-aarch64-apple-darwin.tar.gz"

Пріоритет, у порядку застосування:

  1. exclude відкидає асет остаточно, і ніщо не може його повернути.
  2. Відповідний запис asset.target для цього хоста прямо називає файл, тому він перемагає і include, і власну оцінку платформи.
  3. Інакше фільтрує include, а решту оцінює платформа.

Ключі target — <os>-<arch> машини, на якій запущено ketch, наприклад macos-aarch64, macos-x86_64, linux-x86_64, linux-aarch64, windows-x86_64 або windows-aarch64. Ключа universal немає: universal — властивість асета, а не хоста.

Вдавайтеся до цього, лише коли власна оцінка платформи обирає неправильно. Спершу запустіть ketch info <pkg> --assets — він перелічує кожен асет з отриманою оцінкою та причиною, і зазвичай цього досить, щоб зрозуміти, що має сказати маніфест.

trust

Контрольна сума доводить, що завантаження — це той файл, який зазначено в релізі. Підпис доводить, хто його опублікував. trust називає цього видавця, і ketch відхиляє реліз, що не має його підпису.

[trust]
verifier = "sigstore"
issuer = "https://token.actions.githubusercontent.com"
repository = "BurntSushi/ripgrep"
КлючЗастосовується доЩо робить
verifierусіх — обов'язковоsigstore, minisign або gpg.
modeусіхrequire (типово) відхиляє встановлення, якщо підпис не вдається перевірити. warn однаково встановлює, повідомляє про це й записує пакет як перевірений лише контрольною сумою.
signatureусіхНазва файлу підпису поруч з асетом; {file} позначає назву підписаного файлу. Типово: {file}.sigstore.json, {file}.minisig, {file}.asc.
signedусіхГлоб, що називає список контрольних сум (SHA256SUMS, *checksums.txt), який покриває підпис замість асета. У списку має бути асет із гешем, який завантажив ketch.
issuersigstore — обов'язковоOIDC-видавець сертифіката підпису: https://token.actions.githubusercontent.com для GitHub Actions.
identitysigstoreТочна ідентичність сертифіката: URL workflow, наприклад https://github.com/o/r/.github/workflows/release.yml@refs/tags/v1.2.3, або email.
repositorysigstoreowner/repo: будь-який workflow у цьому репозиторії GitHub. Обов'язково identity, repository або обидва — сам лише видавець пропускає будь-кого, кому він видає сертифікати.
public_keyminisign, gpg — обов'язковоminisign: рядок ключа або весь minisign.pub. gpg: блок відкритого ключа в armored-форматі.
fingerprintgpg — обов'язковоПовний відбиток основного ключа. Він має бути в блоці ключа.

Що приймає кожен верифікатор:

  • sigstore — бандл Sigstore, який пишуть cosign sign-blob --bundle і атестації артефактів GitHub. Він перевіряється офлайн за публічним довіреним коренем Sigstore (public-good trusted root), який ketch має при собі: ланцюжок сертифіката веде до CA Sigstore й називає закріплену ідентичність, підпис покриває файл, а журнал прозорості Rekor підписав запис. Голий .sig із сертифікатом або .intoto.jsonl без запису журналу не можна перевірити офлайн, і вони відхиляються.
  • minisign — підписи з попереднім гешуванням, які робить minisign -S. Застарілі відхиляються.
  • gpg — від'єднаний підпис, armored або двійковий, зроблений ключем або одним із його підключів для підпису. Зв'язка ключів не читається: ключ, імпортований для чогось іншого, не є дозволом цього видавця. Відкликані ключі, прострочені підписи та підписи, зроблені після спливу строку дії ключа, відхиляються.

Файл підпису й будь-який список контрольних сум — такі самі асети релізу, як і той, що встановлюється: реліз, який їх не публікує, провалює встановлення. Те, що було перевірено, записується в state.json — верифікатор, закріплена ідентичність, назва файлу підпису та його SHA-256, а для sigstore — індекс у журналі Rekor, — і ketch info це показує. Нічого з того, що файл підпису каже про себе, не друкується. Пакет без trust відображається як checksum, якщо реліз опублікував відповідну контрольну суму, і як first use, якщо не опублікував жодної.

hooks

Команда, що виконується в кожен із шести моментів життя пакета. Кожна — один рядок для оболонки платформи: sh -c на macOS і Linux, cmd /C у Windows.

[hooks]
before_install = "echo about to install $KETCH_PACKAGE $KETCH_VERSION"
after_install = "./tool --setup"
before_update = "./tool --export > \"$KETCH_ROOT/tool-backup.json\""
after_update = "./tool --import \"$KETCH_ROOT/tool-backup.json\""
before_uninstall = "./tool --teardown"
after_uninstall = "rm -rf \"$HOME/.cache/tool\""
КлючКоли виконується
before_installДо розміщення першого встановлення пакета або перевстановлення вже встановленої версії.
after_installПісля того як це встановлення розміщено, посилання поставлено й усе записано.
before_updateДо того як інша версія замінить встановлену, — ketch upgrade, ketch install іншої версії або ketch rollback.
after_updateПісля того як нову версію розміщено, посилання поставлено й усе записано.
before_uninstallДо видалення посилань пакета та його каталогу в сховищі.
after_uninstallПісля того як їх видалено й пакета більше немає в state.json.

Кожен хук отримує однакове оточення поверх вашого:

ЗміннаЗначення
KETCH_HOOKКлюч, що виконується: after_install тощо.
KETCH_PACKAGEНазва пакета.
KETCH_VERSIONВерсія, яка встановлюється, до якої оновлюють або яка видаляється.
KETCH_PREVIOUS_VERSIONВерсія, що замінюється. Задається лише для двох хуків оновлення.
KETCH_PREFIXКаталог пакета в сховищі. У before_install його ще немає, а в after_uninstall уже немає.
KETCH_BIN_DIRКуди ставляться посилання на бінарники, типово ~/.ketch/bin.
KETCH_ROOTКорінь ketch, типово ~/.ketch.

Робочий каталог — KETCH_PREFIX, коли він існує, тож ./tool у хуку — це щойно розпакований бінарник; інакше хук успадковує каталог, з якого запущено ketch. Стандартне введення закрито. Вивід перехоплюється: він іде в лог, а з --verbose — ще й у термінал.

Хук before_*, що завершився з ненульовим кодом, зупиняє операцію, а його stderr стає подробицями помилки — нічого не розміщується й нічого не видаляється. Хук after_*, що впав, — це попередження: встановлення або видалення вже відбулося, і state.json це відображає, а хук, що впав, не робить це неправдою. Хук, який досі працює через десять хвилин, зупиняється разом з усім, що він запустив, і вважається таким, що впав.

ketch rollback — це оновлення назад до збереженої версії, тому він виконує два хуки оновлення, а KETCH_PREVIOUS_VERSION називає версію, яку залишають. ketch prune видаляє старі копії пакета, що лишається встановленим у тій самій версії, і хуків не виконує.

Хуки виконуються лише з маніфесту у вашому власному каталозі маніфестів, ~/.ketch/manifests/<name>.toml. Пакет реєстру або вбудований маніфест — чужий файл, і встановлення того, що він описує, ніколи не повинно означати запуск чужих команд оболонки, — тому маніфест із цих рівнів, що містить hooks, відхиляється під час встановлення, до розміщення будь-чого, з повідомленням, який файл куди скопіювати. Копіювання його до вашого каталогу маніфестів і є явною згодою: він стає вашим і відтепер перемагає копію з реєстру.

Що перевіряє ketch

Serde перевіряє форму. Ось значення, про які він судити не може:

ПравилоЧому
name має бути придатною дослівно як один компонент шляхуВона стає каталогом у сховищі. Назва, яку довелося б очищати, встановилася б не туди, куди вказує.
кожна bin.name має бути придатною дослівно як один компонент шляхуВона стає символьним посиланням у ~/.ketch/bin.
кожен bin.path має лишатися всередині вмістуЖодних абсолютних шляхів і жодних ...
кожен запис extra_paths має лишатися всередині вмістуТе саме.
кожен запис extra_paths має бути man-сторінкою або автодоповненнямКласифікується за kind або за правилами шляхів нижче. Неоднозначні записи відхиляються.
кожному запису bin потрібен name або pathЗапис без обох нічого не каже.
аліаси provides мають бути непорожніми й без пробільних символівАліас, який ніхто не може набрати, — не аліас.
strip_prefix має бути не більше за 8Кожен рівень — лістинг каталогу, і жоден реальний архів не вкладає обгортки так глибоко.
кожен запис hooks має щось міститиПорожній однаково запустив би оболонку й повідомив би про успіх.
маніфест із hooks має бути з вашого каталогу маніфестів — перевіряється під час встановлення, а не завантаженняМаніфест із реєстру або вбудований — чужий файл, і його команди — чужий код.
trust.signature і trust.signed мають бути придатними назвами файлів, а signature знає лише {file} і має називати інший файлВони називають файли, що завантажуються поруч з асетом.
ключі trust мають належати до обраного verifierfingerprint у політиці minisign мав би вигляд перевірки, але нею не був би.
sigstore потрібен issuer з https та identity або repositoryСам лише видавець пропускає будь-кого, кому він видає сертифікати.
public_key для minisign має розбиратисяКлюч, який не можна прочитати, не можна закріпити.
public_key для gpg має розбиратися, проходити перевірку власних самопідписів, не бути відкликаним і мати закріплений fingerprintВідбиток і є закріпленням; сам по собі блок ключа міг би належати будь-кому.

Маніфест, що не пройшов будь-яку з цих перевірок, відхиляється, а не виправляється. У реєстрі один поганий пакет пропускається з попередженням, а решта однаково завантажуються.

Як випробувати маніфест

Покладіть його в ~/.ketch/manifests/<name>.toml і встановіть:

ketch install <name> --verbose

--verbose повідомляє, з якого рівня прийшов маніфест, який асет було обрано й чому та на що поставлено посилання. Щоб запропонувати його всім, надішліть той самий файл до реєстру як <name>/ketch.toml — див. REGISTRY.md.