diff --git a/src/components/pages/InstallPage.astro b/src/components/pages/InstallPage.astro index 311a87a..05ba480 100644 --- a/src/components/pages/InstallPage.astro +++ b/src/components/pages/InstallPage.astro @@ -46,6 +46,13 @@ const macAsset = platformAsset(["darwin", "macos", ".dmg", ".pkg"]); const winAsset = platformAsset(["windows", "win32", "win64", ".exe", ".msi", ".zip"]); const cardTargets = [macAsset, winAsset]; +/** + * Whether this release ships anything an operating system will refuse to open. Gates the + * unsigned-build note: the wheels are not signed either, but pip does not warn about them, so a + * Gatekeeper explanation shown beside a wheels-only release would be a warning about nothing. + */ +const hasPlatformBundle = Boolean(macAsset || winAsset); + const venvCommands = (os: "mac" | "win"): string => { const whl = "keel_trader-" + (version ?? "") + "-py3-none-any.whl"; if (os === "mac") { @@ -129,6 +136,54 @@ const venvCommands = (os: "mac" | "win"): string => {
+ { + /* + * Shown ONLY when the release actually carries a platform bundle. Until one exists there + * is nothing for an operating system to warn about, and a note about Gatekeeper beside a + * page that only offers wheels would be a warning about nothing. + */ + hasPlatformBundle && ( +
+

{c.unsigned.title}

+

{c.unsigned.lead}

+

{c.unsigned.reason}

+ +
+
+

{c.unsigned.macosTitle}

+
    {c.unsigned.macosSteps.map((step) =>
  1. {step}
  2. )}
+
+
+

{c.unsigned.windowsTitle}

+
    {c.unsigned.windowsSteps.map((step) =>
  1. {step}
  2. )}
+
+
+ +

{c.unsigned.verifyTitle}

+

{c.unsigned.verifyLead}

+
gh attestation verify <the file you downloaded> --repo CodeGateSoftware/keel
+

{c.unsigned.verifyFail}

+ +

{c.unsigned.notMeaningTitle}

+
    {c.unsigned.notMeaning.map((item) =>
  • {item}
  • )}
+ + {/* The friction-free alternative, offered here rather than left further down the + page: someone reading a security warning is deciding whether to proceed, and + "there is a path with no warning at all" is the most useful thing to hand them + at that exact moment. */} +

{c.unsigned.avoidTitle}

+

{c.unsigned.avoidBody}

+

{c.unsigned.avoidLink} →

+ +

+ + {c.unsigned.more} → + +

+
+ ) + } +
{c.fromReleaseWarning} @@ -158,7 +213,7 @@ const venvCommands = (os: "mac" | "win"): string => {

{c.getStarted.link} →

-
+

{c.fromSource.title}

{c.fromSource.lead}

    diff --git a/src/i18n/pages/install.ts b/src/i18n/pages/install.ts index a002325..651232a 100644 --- a/src/i18n/pages/install.ts +++ b/src/i18n/pages/install.ts @@ -18,6 +18,37 @@ export interface PlatformCardCopy { codeComment: string; } +/** + * The unsigned-build note. Shown only when a release actually carries a platform bundle + * (see InstallPage.astro) — until then there is nothing for an OS to warn about. + * + * Apple notarisation needs a Developer ID certificate at $99/yr; keel cannot currently afford + * it, and there is no cheaper tier, no free open-source tier, and no self-signed substitute + * (macOS trusts only Apple-issued certificates). The page says so in those terms: a visitor told + * a build is unsigned with no reason assumes carelessness, and this is simply what is true. + * + * It also refuses to end on "click Open Anyway". keel is a program a visitor may give exchange + * API keys to, so the verification step is part of the note, not a footnote to it. + */ +export interface UnsignedNoteCopy { + title: string; + lead: string; + reason: string; + macosTitle: string; + macosSteps: string[]; + windowsTitle: string; + windowsSteps: string[]; + verifyTitle: string; + verifyLead: string; + verifyFail: string; + notMeaningTitle: string; + notMeaning: string[]; + avoidTitle: string; + avoidBody: string; + avoidLink: string; + more: string; +} + export interface InstallContent { rev: string; title: string; @@ -39,12 +70,13 @@ export interface InstallContent { next: string; }; fromReleaseWarning: string; + unsigned: UnsignedNoteCopy; translatedFromRev?: string; } export const install: LocalizedPage = { en: { - rev: "2026-08-20.2", + rev: "2026-08-20.3", title: "Download keel — macOS & Windows", description: "Download keel for macOS or Windows. Version and links come from GitHub Releases at build time; the five-minute source path is here too.", @@ -83,11 +115,45 @@ export const install: LocalizedPage = { }, fromReleaseWarning: "Never install by bare name. The distribution is keel-trader; the name keel on PyPI belongs to an unrelated project, so pip install keel fetches someone else's package. A build reporting DIRTY or [checkout] is not a release and must not be run against live funds.", + unsigned: { + title: "Your computer will warn you about this download", + lead: "The macOS and Windows builds are not code-signed, so your operating system will refuse the first open and tell you it cannot verify the developer. Nothing is broken, and nothing was detected — your computer simply does not know who wrote the program.", + reason: + "Code signing is a paid certificate. Apple's costs $99 per year, every year, and keel cannot currently afford it. There is no cheaper tier and no free option for open-source projects, and a certificate we made ourselves would do nothing at all, because macOS trusts only certificates Apple issued.", + macosTitle: "macOS", + macosSteps: [ + "Double-click keel. macOS refuses, saying it cannot verify the developer.", + "Open System Settings → Privacy & Security.", + "Scroll to the message about keel and click Open Anyway.", + "You only do this once.", + ], + windowsTitle: "Windows", + windowsSteps: [ + "Run the installer. SmartScreen says \"Windows protected your PC\".", + "Click More info, then Run anyway.", + ], + verifyTitle: "Please check what you downloaded first", + verifyLead: + "We would rather not simply ask you to click past a security warning — keel is a program you may give exchange API keys to. Every release carries proof of where its files came from, which answers the same question a certificate answers: was this built from keel's own source, by keel's own release pipeline? A SHA256SUMS.txt file is attached to every release too.", + verifyFail: + "If either check fails, do not open the file. A failing check means it is not the file we built, and no amount of clicking Open Anyway makes that safe.", + notMeaningTitle: "What this does not mean", + notMeaning: [ + "It does not mean the download is damaged.", + "It does not mean your computer found something wrong. Nothing was scanned and nothing was detected.", + "It does not mean the app behaves differently. A signed and an unsigned build of the same release are the same program.", + ], + avoidTitle: "Or skip the warning entirely", + avoidBody: + "The five-minute source path below installs the same engine with no installer and no warning, because nothing is downloaded as an application — pip and uv fetch the published wheels directly. It needs a terminal and Python 3.11+, which is exactly the friction the desktop app exists to remove; but if you already have both, it is the shorter road.", + avoidLink: "Try it in five minutes", + more: "Full explanation, including how to verify what you downloaded", + }, }, ar: { - rev: "2026-08-20.2", - translatedFromRev: "2026-08-20.2", + rev: "2026-08-20.3", + translatedFromRev: "2026-08-20.3", title: "تنزيل كيل — macOS وWindows", description: "نزّل كيل لـ macOS أو Windows. الإصدار والروابط من GitHub Releases وقت البناء؛ ومسار المصدر في خمس دقائق هنا أيضًا.", @@ -126,11 +192,45 @@ export const install: LocalizedPage = { }, fromReleaseWarning: "لا تثبّت بالاسم المجرد أبدًا. التوزيعة اسمها keel-trader؛ أما اسم keel على PyPI فلمشروعٍ آخر لا علاقة له بنا، فأمر pip install keel يجلب حزمة غيرنا. والبنية التي تُظهر DIRTY أو [checkout] ليست إصدارًا ويُمنع تشغيلها على أموالٍ حيّة.", + unsigned: { + title: "سيحذّرك جهازك من هذا التنزيل", + lead: "بِنى macOS وWindows غير موقّعة رقميًّا، لذا سيرفض نظامك فتحها أول مرّة ويقول إنه لا يستطيع التحقّق من المطوّر. لا شيء معطّل، ولم يُكتشف شيء — جهازك ببساطة لا يعرف من كتب البرنامج.", + reason: + "التوقيع الرقمي شهادةٌ مدفوعة. شهادة Apple تكلّف 99 دولارًا سنويًّا، كلّ سنة، وكيل لا يقدر عليها حاليًّا. ولا توجد فئة أرخص ولا خيارٌ مجانيٌّ لمشاريع المصدر المفتوح، وشهادةٌ نصنعها بأنفسنا لا تفيد شيئًا البتّة، لأن macOS لا يثق إلا بالشهادات الصادرة عن Apple.", + macosTitle: "macOS", + macosSteps: [ + "انقر كيل نقرًا مزدوجًا. سيرفض macOS قائلًا إنه لا يستطيع التحقّق من المطوّر.", + "افتح ‏System Settings ← Privacy & Security.", + "انزل إلى الرسالة الخاصّة بكيل واضغط ‏Open Anyway.", + "تفعل هذا مرّةً واحدة فقط.", + ], + windowsTitle: "Windows", + windowsSteps: [ + "شغّل المثبّت. سيقول ‏SmartScreen‏: «‏Windows protected your PC‏».", + "اضغط ‏More info ثم ‏Run anyway.", + ], + verifyTitle: "من فضلك تحقّق ممّا نزّلته أولًا", + verifyLead: + "لا نحبّ أن نطلب منك مجرّد تجاوز تحذيرٍ أمني — فكيل برنامجٌ قد تعطيه مفاتيح API لمنصّة تداول. كلّ إصدارٍ يحمل إثباتًا لمصدر ملفّاته، وهو يجيب عن السؤال نفسه الذي تجيب عنه الشهادة: هل بُني هذا من مصدر كيل نفسه، عبر خطّ إصدار كيل نفسه؟ ويُرفق بكلّ إصدارٍ ملفّ SHA256SUMS.txt أيضًا.", + verifyFail: + "إن فشل أيٌّ من الفحصين فلا تفتح الملفّ. الفشل يعني أنه ليس الملفّ الذي بنيناه، ولن يجعله الضغط على ‏Open Anyway آمنًا.", + notMeaningTitle: "ما لا يعنيه هذا التحذير", + notMeaning: [ + "لا يعني أن التنزيل تالف.", + "لا يعني أن جهازك وجد خطبًا ما. لم يُفحص شيء ولم يُكتشف شيء.", + "لا يعني أن التطبيق يسلك سلوكًا مختلفًا. البنية الموقّعة وغير الموقّعة من الإصدار نفسه هما البرنامج نفسه.", + ], + avoidTitle: "أو تجاوز التحذير من أصله", + avoidBody: + "مسار المصدر في خمس دقائق أدناه يثبّت المحرّك نفسه بلا مثبّت وبلا تحذير، لأنه لا يُنزَّل شيءٌ بوصفه تطبيقًا — بل يجلب pip وuv العجلات المنشورة مباشرةً. يحتاج طرفيّةً وPython 3.11 فأحدث، وهو بالضبط الاحتكاك الذي وُجد تطبيق سطح المكتب ليزيله؛ لكن إن كان كلاهما عندك أصلًا فهو الطريق الأقصر.", + avoidLink: "جرّبه في خمس دقائق", + more: "الشرح الكامل، وكيف تتحقّق ممّا نزّلته", + }, }, fr: { - rev: "2026-08-20.1", - translatedFromRev: "2026-08-20.2", + rev: "2026-08-20.3", + translatedFromRev: "2026-08-20.3", title: "Télécharger keel — macOS et Windows", description: "Téléchargez keel pour macOS ou Windows. Version et liens viennent de GitHub Releases au build ; le parcours en cinq minutes depuis les sources est là aussi.", @@ -169,5 +269,39 @@ export const install: LocalizedPage = { }, fromReleaseWarning: "N'installez jamais par nom nu. La distribution s'appelle keel-trader ; le nom keel sur PyPI appartient à un projet sans rapport, donc pip install keel récupère le paquet de quelqu'un d'autre. Un build qui affiche DIRTY ou [checkout] n'est pas une version et ne doit jamais tourner sur des fonds réels.", + unsigned: { + title: "Votre ordinateur va vous avertir à propos de ce téléchargement", + lead: "Les builds macOS et Windows ne sont pas signés, donc votre système refusera la première ouverture en disant qu'il ne peut pas vérifier le développeur. Rien n'est cassé et rien n'a été détecté — votre ordinateur ne sait simplement pas qui a écrit le programme.", + reason: + "La signature de code est un certificat payant. Celui d'Apple coûte 99 $ par an, chaque année, et keel n'en a pas les moyens aujourd'hui. Il n'existe ni offre moins chère ni option gratuite pour les projets open source, et un certificat que nous fabriquerions nous-mêmes ne servirait à rien : macOS ne fait confiance qu'aux certificats émis par Apple.", + macosTitle: "macOS", + macosSteps: [ + "Double-cliquez sur keel. macOS refuse, en disant qu'il ne peut pas vérifier le développeur.", + "Ouvrez Réglages Système → Confidentialité et sécurité.", + "Descendez jusqu'au message concernant keel et cliquez sur Ouvrir quand même.", + "Vous ne le faites qu'une seule fois.", + ], + windowsTitle: "Windows", + windowsSteps: [ + "Lancez l'installeur. SmartScreen affiche « Windows a protégé votre ordinateur ».", + "Cliquez sur Informations complémentaires, puis Exécuter quand même.", + ], + verifyTitle: "Vérifiez d'abord ce que vous avez téléchargé", + verifyLead: + "Nous préférons ne pas vous demander simplement de passer outre un avertissement de sécurité — keel est un programme auquel vous confierez peut-être des clés API d'exchange. Chaque version porte une preuve de l'origine de ses fichiers, qui répond à la même question qu'un certificat : ceci a-t-il été construit depuis les sources de keel, par le pipeline de publication de keel ? Un fichier SHA256SUMS.txt est également joint à chaque version.", + verifyFail: + "Si l'une des deux vérifications échoue, n'ouvrez pas le fichier. Un échec signifie que ce n'est pas le fichier que nous avons construit, et aucun « Ouvrir quand même » ne rendra cela sûr.", + notMeaningTitle: "Ce que cela ne signifie pas", + notMeaning: [ + "Cela ne signifie pas que le téléchargement est endommagé.", + "Cela ne signifie pas que votre ordinateur a trouvé un problème. Rien n'a été analysé et rien n'a été détecté.", + "Cela ne signifie pas que l'application se comporte différemment. Un build signé et un build non signé de la même version sont le même programme.", + ], + avoidTitle: "Ou évitez complètement l'avertissement", + avoidBody: + "Le parcours en cinq minutes depuis les sources, ci-dessous, installe le même moteur sans installeur et sans avertissement, parce que rien n'est téléchargé en tant qu'application — pip et uv récupèrent directement les wheels publiées. Il faut un terminal et Python 3.11+, ce qui est précisément la friction que l'application de bureau existe pour supprimer ; mais si vous avez déjà les deux, c'est le chemin le plus court.", + avoidLink: "Essayez-le en cinq minutes", + more: "Explication complète, et comment vérifier ce que vous avez téléchargé", + }, }, }; diff --git a/src/styles/global.css b/src/styles/global.css index 60a788d..d5f77f1 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -922,6 +922,51 @@ section { margin-bottom: 0.45em; } +/* -------------------------------------------------------------------------- + Unsigned-build note (install page) + + Reuses the notice palette rather than an alarm colour. The message is "your + computer will stop you, here is why and here is how to check what you have" + -- not "something is wrong". A red box would say the second. + -------------------------------------------------------------------------- */ + +.unsigned-note { + border: 1px solid var(--notice-border); + background: var(--notice-bg); + color: var(--notice-ink); + border-radius: var(--radius); + padding: 1.4rem 1.6rem; + margin-block: 2rem; +} + +.unsigned-note h2 { + margin-top: 0; + font-size: 1.15rem; +} + +.unsigned-note h3 { + font-size: 0.95rem; + margin-block: 1.4rem 0.4rem; +} + +.unsigned-steps { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); + gap: 0 2rem; +} + +.unsigned-steps h3 { + margin-top: 1.2rem; +} + +.verify-command { + overflow-x: auto; + font-size: 0.85rem; + padding: 0.7rem 0.9rem; + border-radius: var(--radius); + background: var(--code-bg, rgba(0, 0, 0, 0.06)); +} + /* -------------------------------------------------------------------------- Stale translation banner (FR-8) -------------------------------------------------------------------------- */