docs(readme,landing): clearer install guidance with first-launch unsigned-build FAQ (#212)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Yaowei Zheng
2026-08-05 12:05:39 +08:00
committed by GitHub
parent a78901d70e
commit c6fb255a3b
6 changed files with 243 additions and 30 deletions
+53 -9
View File
@@ -112,16 +112,64 @@ Each family's latest generation only — the app's **Models** page lists every b
## Installation
Every route installs the same `penguin` command: `penguin web` launches the full Web experience (multi-session chat, agent/skill/model management, usage stats, Trace observability, evaluation center; first login is `admin` with the initial password printed on the server's first start, of the form `penguin-1234` — change it right after), and models are configured on the in-app Models page. The online installers bundle their own Node runtime — unpack and run; upgrades and reinstalls never touch your data.
Two ways in — both work on the same `~/.penguin/data` root, so a desktop install and a CLI install can be mixed freely:
### 🐧 Linux (online install)
- **🖥️ Desktop app** — a double-click install: it embeds the server and opens already signed in, no terminal involved.
- **⌨️ CLI** — a one-line installer (or npm / offline package) puts the `penguin` command on the machine; `penguin web` then serves the full Web experience in your browser at `http://127.0.0.1:7364` (multi-session chat, agent / skill / model management, usage stats, Trace observability, evaluation center). The online installers bundle their own Node runtime — unpack and run; upgrades and reinstalls never touch your data.
> [!NOTE]
> On a CLI install, the first Web login is `admin`, with the initial password printed on the server's first start (of the form `penguin-1234`) — change it right after. Models are configured on the in-app **Models** page.
### 🖥️ Desktop app
The full Web experience as a standalone application: it embeds the server and opens already signed in — no terminal, no login page, no initial password to copy. It works on the same `~/.penguin/data` root as a CLI install, so the two can be used interchangeably (a data root only ever runs one server; if a CLI-started instance is already up, the app attaches to it).
**[⬇️ Get it from the download page](https://penguin.ooo/download)** — the page serves the OSS-accelerated mirror when it is reachable, and every installer is also attached to each [GitHub Release](https://github.com/Prism-Shadow/penguin-harness/releases).
| Platform | Installers |
| ----------- | --------------------------- |
| macOS 11+ | dmg (Apple Silicon / Intel) |
| Windows 10+ | installer (.exe, x64) |
| Linux (x64) | AppImage / deb |
Current builds are unsigned, so the system may block the very first launch. Expand your platform for the one-time fix:
<details>
<summary><b>🍎 macOS says “PenguinHarness” is damaged and can’t be opened</b></summary>
macOS quarantines files downloaded from the internet, and the missing signature makes that flag surface as a false “damaged” alert. Deleting the flag clears it:
1. Open the downloaded dmg and drag `PenguinHarness.app` into the **Applications** folder.
2. Open **Terminal** (Launchpad → Other → Terminal).
3. Paste this command into Terminal and press Enter, then type your login password (nothing shows while you type; press Enter when done):
```bash
curl -fsSL https://penguin.ooo/install.sh | sh
penguin web # start the service and open http://127.0.0.1:7364
sudo xattr -rd com.apple.quarantine /Applications/PenguinHarness.app
```
### 🍎 macOS (online install)
4. Once it finishes, double-click the app — it now opens normally.
</details>
<details>
<summary><b>🪟 Windows SmartScreen says “Windows protected your PC”</b></summary>
The installer is not signed yet, so SmartScreen holds the first run: click **More info**, then **Run anyway** to continue installing — first run only.
</details>
<details>
<summary><b>🐧 Linux: double-clicking the AppImage does nothing</b></summary>
Browsers download AppImages without the execute permission. Grant it once and the app starts normally from then on (the deb package installs through the package manager and is not affected):
```bash
chmod +x penguin-desktop-linux-x86_64.AppImage
```
</details>
### 🐧🍎 Linux / macOS (online install)
```bash
curl -fsSL https://penguin.ooo/install.sh | sh
@@ -142,10 +190,6 @@ npm install -g @prismshadow/penguin-cli
penguin web # start the service and open http://127.0.0.1:7364
```
### 🖥️ Desktop app
The full Web experience as a standalone application: it embeds the server and opens already signed in — no terminal, no login page, no initial password to copy — and it works on the same `~/.penguin/data` root as a CLI install, so the two can be used interchangeably (a data root only ever runs one server; if a CLI-started instance is already up, the app attaches to it). Installers for macOS (dmg, Apple silicon / Intel), Windows and Linux (AppImage / deb) live on the [download page](https://penguin.ooo/download) — which serves the OSS-accelerated mirror when it is reachable — and are attached to each [GitHub Release](https://github.com/Prism-Shadow/penguin-harness/releases). Current builds are unsigned: on first launch use right-click → Open on macOS, and "More info → Run anyway" past Windows SmartScreen.
<details>
<summary><b>📴 Offline install (air-gapped machines)</b></summary>
+53 -9
View File
@@ -112,16 +112,64 @@ https://github.com/user-attachments/assets/aec49ae9-b743-467b-b247-37bedfeaa36e
## 安装
每种方式装出的都是同一个 `penguin` 命令:`penguin web` 启动完整 Web 体验(多会话对话、Agent / 技能 / 模型管理、用量统计、轨迹观测、评估中心;首次登录使用 `admin`,初始密码在服务端首次启动时打印,形如 `penguin-1234`,登录后请尽快修改密码),在应用内模型页配置模型后即可对话。在线安装器自带 Node 运行时,解压即用,升级与重装不触碰数据。
两条路线——数据同在 `~/.penguin/data` 目录,桌面端与命令行安装可自由混用:
### 🐧 Linux(在线安装)
- **🖥️ 桌面端应用**——双击安装:内嵌服务端,打开即已登录,全程无需终端。
- **⌨️ 命令行**——一行命令(或 npm / 离线包)装出 `penguin` 命令,`penguin web` 即在浏览器打开完整 Web 体验 `http://127.0.0.1:7364`(多会话对话、Agent / 技能 / 模型管理、用量统计、轨迹观测、评估中心)。在线安装器自带 Node 运行时,解压即用;升级与重装不触碰数据。
> [!NOTE]
> 命令行安装后,Web 首次登录用户名为 `admin`,初始密码在服务端首次启动时打印(形如 `penguin-1234`),登录后请尽快修改;模型在应用内「模型」页配置。
### 🖥️ 桌面端应用
完整的 Web 体验打包为独立应用:内嵌服务端,打开即已登录——无需终端、无登录页、也不用抄初始密码——并与 CLI 安装共用同一个 `~/.penguin/data` 数据目录,两者可以混用(一个数据目录同一时刻只运行一个服务端;CLI 已启动实例时,应用会直接接入它)。
**[⬇️ 前往下载页获取](https://penguin.ooo/download)**——国内自动走 OSS 镜像加速,安装包也附于每个 [GitHub Release](https://github.com/Prism-Shadow/penguin-harness/releases)。
| 平台 | 安装包 |
| ------------- | ---------------------------- |
| macOS 11+ | dmg(Apple 芯片 / Intel) |
| Windows 10+ | 安装程序(.exe,x64) |
| Linux(x64) | AppImage / deb |
当前构建暂未签名,系统可能拦截首次启动。展开对应系统的步骤,操作一次即可解除:
<details>
<summary><b>🍎 macOS 提示「PenguinHarness」已损坏,无法打开?</b></summary>
macOS 会给从网络下载的文件加上隔离标记,应用未签名时会因此被误报「已损坏」。删除该标记即可解除:
1. 打开下载的 dmg,把 `PenguinHarness.app` 拖入「应用程序(Applications)」文件夹。
2. 打开终端:「启动台 → 其他 → 终端」。
3. 在终端粘贴这条命令并回车,然后输入开机密码(输入时屏幕不显示字符,输完回车即可):
```bash
curl -fsSL https://penguin.ooo/install.sh | sh
penguin web # 启动服务并打开 http://127.0.0.1:7364
sudo xattr -rd com.apple.quarantine /Applications/PenguinHarness.app
```
### 🍎 macOS(在线安装)
4. 执行完成后,双击即可正常打开应用。
</details>
<details>
<summary><b>🪟 Windows SmartScreen 提示「Windows 已保护你的电脑」?</b></summary>
安装程序暂未签名,SmartScreen 会拦截首次运行:点「更多信息」,再点「仍要运行」即可继续安装,仅首次运行需要。
</details>
<details>
<summary><b>🐧 Linux 双击 AppImage 没有反应?</b></summary>
浏览器下载的 AppImage 默认没有执行权限,赋权一次后即可正常启动(deb 包经包管理器安装,无此问题):
```bash
chmod +x penguin-desktop-linux-x86_64.AppImage
```
</details>
### 🐧🍎 Linux / macOS(在线安装)
```bash
curl -fsSL https://penguin.ooo/install.sh | sh
@@ -142,10 +190,6 @@ npm install -g @prismshadow/penguin-cli
penguin web # 启动服务并打开 http://127.0.0.1:7364
```
### 🖥️ 桌面端应用
完整的 Web 体验打包为独立应用:内嵌服务端,打开即已登录——无需终端、无登录页、也不用抄初始密码——并与 CLI 安装共用同一个 `~/.penguin/data` 数据目录,两者可以混用(一个数据目录同一时刻只运行一个服务端;CLI 已启动实例时,应用会直接接入它)。macOS(dmg,Apple 芯片 / Intel)、Windows 与 Linux(AppImage / deb)的安装包可在[下载页](https://penguin.ooo/download)获取——国内自动走 OSS 镜像加速——也附于每个 [GitHub Release](https://github.com/Prism-Shadow/penguin-harness/releases)。当前构建暂未签名:macOS 首次启动请右键 →「打开」,Windows 在 SmartScreen 提示中选「仍要运行」。
<details>
<summary><b>📴 离线安装(无网环境)</b></summary>
+17 -4
View File
@@ -102,6 +102,12 @@ export interface DesktopInstaller {
variant: string;
}
/** Named separately: the first-launch FAQ's chmod command quotes this exact file name. */
const LINUX_APPIMAGE: DesktopInstaller = {
file: "penguin-desktop-linux-x86_64.AppImage",
variant: "AppImage",
};
/** Installers per platform card, in display order. */
export const DESKTOP_INSTALLERS: Record<"mac" | "windows" | "linux", DesktopInstaller[]> = {
mac: [
@@ -109,11 +115,18 @@ export const DESKTOP_INSTALLERS: Record<"mac" | "windows" | "linux", DesktopInst
{ file: "penguin-desktop-darwin-x64.dmg", variant: "Intel" },
],
windows: [{ file: "penguin-desktop-win32-x64.exe", variant: "x64" }],
linux: [
{ file: "penguin-desktop-linux-x86_64.AppImage", variant: "AppImage" },
{ file: "penguin-desktop-linux-amd64.deb", variant: "deb" },
],
linux: [LINUX_APPIMAGE, { file: "penguin-desktop-linux-amd64.deb", variant: "deb" }],
};
/** Checksum list covering every desktop installer of a release. */
export const DESKTOP_SHA256SUMS = "SHA256SUMS.desktop";
/**
* First-launch fixes for the unsigned desktop builds (the download page FAQ).
* Language-neutral, like the install commands above. The macOS one deletes the
* quarantine flag that makes Gatekeeper report the app as "damaged"; the Linux
* one restores the execute bit browsers strip from a downloaded AppImage.
*/
export const MAC_UNQUARANTINE_CMD =
"sudo xattr -rd com.apple.quarantine /Applications/PenguinHarness.app";
export const LINUX_APPIMAGE_CHMOD_CMD = `chmod +x ${LINUX_APPIMAGE.file}`;
+25 -2
View File
@@ -105,8 +105,31 @@ export const en: Strings = {
altOss: "Use the OSS mirror instead",
checksums: "Checksums (SHA256SUMS.desktop)",
allReleases: "All releases",
unsignedNote:
'Current builds are unsigned: on first launch use right-click → Open on macOS, and "More info → Run anyway" past Windows SmartScreen.',
/** First-launch FAQ: one collapsible item per platform, the visitor's own pre-expanded. */
faq: {
title: "First-launch FAQ",
intro:
"Current builds are not signed yet, so the system may block the very first launch — the fix for your platform below is needed only once.",
mac: {
question: "macOS says “PenguinHarness” is damaged and can’t be opened?",
why: "macOS quarantines files downloaded from the internet, and the missing signature makes that flag surface as a false “damaged” alert. Deleting the flag clears it:",
stepDrag: "Open the downloaded dmg and drag PenguinHarness into the Applications folder.",
stepTerminal: "Open Terminal (Launchpad → Other → Terminal).",
stepPaste:
"Paste this command into Terminal and press Enter, then type your login password (nothing shows while you type; press Enter when done):",
stepOpen: "Once it finishes, double-click the app — it now opens normally.",
},
windows: {
question: "Windows SmartScreen says “Windows protected your PC”?",
answer:
"The installer is not signed yet, so SmartScreen holds the first run: click “More info”, then “Run anyway” to continue installing — first run only.",
},
linux: {
question: "Nothing happens when double-clicking the AppImage on Linux?",
answer:
"Browsers download AppImages without the execute permission. Grant it once and the app starts normally from then on (the deb package installs through the package manager and is not affected):",
},
},
cliHint: "Just need the CLI, or the Web UI in a browser?",
cliHintLink: "See the quick start",
},
+24 -2
View File
@@ -107,8 +107,30 @@ export const zh = {
altOss: "改用 OSS 镜像下载",
checksums: "校验和(SHA256SUMS.desktop)",
allReleases: "全部版本",
unsignedNote:
"当前构建暂未签名:macOS 首次启动请右键 →「打开」;Windows 在 SmartScreen 提示中选「更多信息 → 仍要运行」。",
/** First-launch FAQ: one collapsible item per platform, the visitor's own pre-expanded. */
faq: {
title: "首次启动常见问题",
intro: "当前构建暂未签名,系统可能拦截首次启动——按对应系统的步骤解除即可,只需操作一次。",
mac: {
question: "macOS 提示「PenguinHarness」已损坏,无法打开?",
why: "macOS 会给从网络下载的文件加上隔离标记,应用未签名时会因此被误报「已损坏」。删除该标记即可解除:",
stepDrag: "打开下载的 dmg,把 PenguinHarness 拖入「应用程序(Applications)」文件夹。",
stepTerminal: "打开终端:「启动台 → 其他 → 终端」。",
stepPaste:
"在终端粘贴这条命令并回车,然后输入开机密码(输入时屏幕不显示字符,输完回车即可):",
stepOpen: "执行完成后,双击即可正常打开应用。",
},
windows: {
question: "Windows SmartScreen 提示「Windows 已保护你的电脑」?",
answer:
"安装程序暂未签名,SmartScreen 会拦截首次运行:点「更多信息」,再点「仍要运行」即可继续安装,仅首次运行需要。",
},
linux: {
question: "Linux 双击 AppImage 没有反应?",
answer:
"浏览器下载的 AppImage 默认没有执行权限,赋权一次后即可正常启动(deb 包经包管理器安装,无此问题):",
},
},
cliHint: "只需要命令行或浏览器里的 Web 界面?",
cliHintLink: "参见快速开始",
},
+71 -4
View File
@@ -5,20 +5,27 @@
* resolves (validated the same way the installers validate it; any failure, e.g. CORS
* not configured on the bucket or the mirror unreachable, silently keeps the GitHub
* links). Plain-anchor downloads are never CORS-gated — only this version lookup is.
* Below the cards, a first-launch FAQ covers the unsigned builds — one collapsible item
* per platform (macOS quarantine removal, SmartScreen, AppImage execute bit), with the
* visitor's own platform pre-expanded.
*/
import { useEffect, useState } from "react";
import { Link } from "react-router";
import type { ReactNode } from "react";
import { S } from "../lib/strings";
import {
DESKTOP_INSTALLERS,
DESKTOP_SHA256SUMS,
GITHUB_LATEST_DOWNLOAD,
LINUX_APPIMAGE_CHMOD_CMD,
MAC_UNQUARANTINE_CMD,
OSS_LATEST_JSON_URL,
OSS_ORIGIN,
RELEASES_URL,
} from "../lib/links";
import { Section } from "../components/section";
import { DownloadIcon, ExternalLinkIcon } from "../components/icons";
import { CodeCard } from "../components/code-card";
import { ChevronDownIcon, DownloadIcon, ExternalLinkIcon } from "../components/icons";
type Platform = "mac" | "windows" | "linux";
const PLATFORMS: Platform[] = ["mac", "windows", "linux"];
@@ -37,6 +44,36 @@ interface Mirror {
base: string;
}
/**
* One collapsible item of the first-launch FAQ. `defaultOpen` pre-expands the visitor's
* own platform on mount; after that the element owns its open state (React only writes
* the `open` property again if the rendered value changes, which it never does here).
*/
function FaqItem({
question,
defaultOpen,
children,
}: {
question: string;
defaultOpen: boolean;
children: ReactNode;
}) {
return (
<details
open={defaultOpen}
className="group rounded-xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-gray-900"
>
<summary className="flex cursor-pointer list-none items-center justify-between gap-3 px-4 py-3 text-sm font-medium tracking-tight [&::-webkit-details-marker]:hidden">
{question}
<ChevronDownIcon className="h-4 w-4 shrink-0 text-gray-400 transition-transform group-open:rotate-180 dark:text-gray-500" />
</summary>
<div className="border-t border-gray-200 px-4 py-3 text-sm leading-6 text-gray-600 dark:border-gray-800 dark:text-gray-400">
{children}
</div>
</details>
);
}
/** latest.json validated like the installer forwarders validate it: schema 1, safe v-tag, fixed bucket base. */
function parseMirror(value: unknown): Mirror | null {
if (typeof value !== "object" || value === null) return null;
@@ -135,10 +172,40 @@ export function DownloadPage() {
<ExternalLinkIcon className="h-3 w-3" />
</a>
</p>
<p className="mt-6 text-xs leading-5 text-gray-500 dark:text-gray-400">
{S.download.unsignedNote}
</div>
<div className="mx-auto mt-12 max-w-2xl">
<h3 className="text-center text-base font-semibold tracking-tight">
{S.download.faq.title}
</h3>
<p className="mt-1 text-center text-sm leading-6 text-gray-600 dark:text-gray-400">
{S.download.faq.intro}
</p>
<p className="mt-2 text-xs leading-5 text-gray-500 dark:text-gray-400">
<div className="mt-4 flex flex-col gap-3 text-left">
<FaqItem question={S.download.faq.mac.question} defaultOpen={detected === "mac"}>
<p>{S.download.faq.mac.why}</p>
<ol className="mt-2 flex list-decimal flex-col gap-1.5 pl-5">
<li>{S.download.faq.mac.stepDrag}</li>
<li>{S.download.faq.mac.stepTerminal}</li>
<li>
{S.download.faq.mac.stepPaste}
<CodeCard code={MAC_UNQUARANTINE_CMD} label="Terminal" className="mt-2 mb-1" />
</li>
<li>{S.download.faq.mac.stepOpen}</li>
</ol>
</FaqItem>
<FaqItem question={S.download.faq.windows.question} defaultOpen={detected === "windows"}>
<p>{S.download.faq.windows.answer}</p>
</FaqItem>
<FaqItem question={S.download.faq.linux.question} defaultOpen={detected === "linux"}>
<p>{S.download.faq.linux.answer}</p>
<CodeCard code={LINUX_APPIMAGE_CHMOD_CMD} label="shell" className="mt-2" />
</FaqItem>
</div>
</div>
<div className="mx-auto mt-10 max-w-4xl text-center">
<p className="text-xs leading-5 text-gray-500 dark:text-gray-400">
{S.download.cliHint}{" "}
<Link to="/#quickstart" className={textLink}>
{S.download.cliHintLink}