Маніфести
Маніфест — це конфігурація одного пакета: звідки він береться, який асет релізу брати й що виставляти назовні після розпакування.
Він потрібен рідко. ketch виводить маніфест з одного лише owner/repo, і для більшості
проєктів цього досить. Пишіть маніфест, коли виведення помиляється: асет із
незвичною назвою, бінарник, на який треба поставити посилання під іншою назвою,
бандл .app, короткий аліас, який варто запам'ятати.
Та сама схема використовується в трьох місцях:
| Де | Файл | Для кого |
|---|---|---|
| Ваша машина | ~/.ketch/manifests/<name>.toml | лише для вас |
| Реєстр пакетів | <package>/ketch.toml | для всіх — див. REGISTRY.md |
| Вбудовано в ketch | crates/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 для людей. |
kind | auto | binary | app | auto | Чим є вміст. Див. нижче. |
provides | список рядків | порожньо | Інші назви, на які відгукується цей пакет. ketch install rg працює, бо ripgrep надає rg. |
prerelease | bool | false | Враховувати пререлізи під час розв'язання 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 — отримують посилання як завжди.
Вибір такий:
- той, що названий у
--bin <name>дляketch installабоketch upgrade, без урахування регістру та.exe; назва, що не збігається з жодним бінарником релізу, — помилка; - інакше той, чия назва збігається з назвою пакета, —
rtok, але ніколи неrtok-hook, — без урахування регістру та.exe; - інакше той, що був обраний для цього пакета раніше:
state.jsonпам'ятає його між оновленнями, перевстановленнями,ketch linkіketch rollback, аketch syncтакож читає його з ключаbinуketch.lock(спершу стан, потім lock-файл); - інакше той, який ви оберете з нумерованого списку, коли ketch працює в
терміналі й
--yesне передано; - інакше нічого: встановлення завершується помилкою зі списком кандидатів і записом
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"
Пріоритет, у порядку застосування:
excludeвідкидає асет остаточно, і ніщо не може його повернути.- Відповідний запис
asset.targetдля цього хоста прямо називає файл, тому він перемагає іinclude, і власну оцінку платформи. - Інакше фільтрує
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. |
issuer | sigstore — обов'язково | OIDC-видавець сертифіката підпису: https://token.actions.githubusercontent.com для GitHub Actions. |
identity | sigstore | Точна ідентичність сертифіката: URL workflow, наприклад https://github.com/o/r/.github/workflows/release.yml@refs/tags/v1.2.3, або email. |
repository | sigstore | owner/repo: будь-який workflow у цьому репозиторії GitHub. Обов'язково identity, repository або обидва — сам лише видавець пропускає будь-кого, кому він видає сертифікати. |
public_key | minisign, gpg — обов'язково | minisign: рядок ключа або весь minisign.pub. gpg: блок відкритого ключа в armored-форматі. |
fingerprint | gpg — обов'язково | Повний відбиток основного ключа. Він має бути в блоці ключа. |
Що приймає кожен верифікатор:
- 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 мають належати до обраного verifier | fingerprint у політиці 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.