From c6fb255a3ba8c61736e756b09cfedf807ab0973a Mon Sep 17 00:00:00 2001
From: Yaowei Zheng 🍎 macOS says “PenguinHarness” is damaged and can’t be opened
+
+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
+ sudo xattr -rd com.apple.quarantine /Applications/PenguinHarness.app
+ ```
+
+4. Once it finishes, double-click the app — it now opens normally.
+
+🪟 Windows SmartScreen says “Windows protected your PC”
+
+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: double-clicking the AppImage does nothing
+
+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
-curl -fsSL https://penguin.ooo/install.sh | sh
-penguin web # start the service and open http://127.0.0.1:7364
+chmod +x penguin-desktop-linux-x86_64.AppImage
```
-### 🍎 macOS (online install)
+📴 Offline install (air-gapped machines)
diff --git a/README.zh.md b/README.zh.md
index 60d3543..97d11f1 100644
--- a/README.zh.md
+++ b/README.zh.md
@@ -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 |
+
+当前构建暂未签名,系统可能拦截首次启动。展开对应系统的步骤,操作一次即可解除:
+
+🍎 macOS 提示「PenguinHarness」已损坏,无法打开?
+
+macOS 会给从网络下载的文件加上隔离标记,应用未签名时会因此被误报「已损坏」。删除该标记即可解除:
+
+1. 打开下载的 dmg,把 `PenguinHarness.app` 拖入「应用程序(Applications)」文件夹。
+2. 打开终端:「启动台 → 其他 → 终端」。
+3. 在终端粘贴这条命令并回车,然后输入开机密码(输入时屏幕不显示字符,输完回车即可):
+
+ ```bash
+ sudo xattr -rd com.apple.quarantine /Applications/PenguinHarness.app
+ ```
+
+4. 执行完成后,双击即可正常打开应用。
+
+🪟 Windows SmartScreen 提示「Windows 已保护你的电脑」?
+
+安装程序暂未签名,SmartScreen 会拦截首次运行:点「更多信息」,再点「仍要运行」即可继续安装,仅首次运行需要。
+
+🐧 Linux 双击 AppImage 没有反应?
+
+浏览器下载的 AppImage 默认没有执行权限,赋权一次后即可正常启动(deb 包经包管理器安装,无此问题):
```bash
-curl -fsSL https://penguin.ooo/install.sh | sh
-penguin web # 启动服务并打开 http://127.0.0.1:7364
+chmod +x penguin-desktop-linux-x86_64.AppImage
```
-### 🍎 macOS(在线安装)
+📴 离线安装(无网环境)
diff --git a/packages/landing/src/lib/links.ts b/packages/landing/src/lib/links.ts
index 99d5546..19e8482 100644
--- a/packages/landing/src/lib/links.ts
+++ b/packages/landing/src/lib/links.ts
@@ -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}`;
diff --git a/packages/landing/src/lib/strings-en.ts b/packages/landing/src/lib/strings-en.ts
index 7d45895..a30fadd 100644
--- a/packages/landing/src/lib/strings-en.ts
+++ b/packages/landing/src/lib/strings-en.ts
@@ -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",
},
diff --git a/packages/landing/src/lib/strings.ts b/packages/landing/src/lib/strings.ts
index 4c99af9..07fc4ce 100644
--- a/packages/landing/src/lib/strings.ts
+++ b/packages/landing/src/lib/strings.ts
@@ -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: "参见快速开始",
},
diff --git a/packages/landing/src/pages/download.tsx b/packages/landing/src/pages/download.tsx
index 4375bc8..1ec67cd 100644
--- a/packages/landing/src/pages/download.tsx
+++ b/packages/landing/src/pages/download.tsx
@@ -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 (
+
+ {question}
+
+
- {S.download.unsignedNote} + + +
+ {S.download.faq.intro}
-+
{S.download.faq.mac.why}
+{S.download.faq.windows.answer}
+{S.download.faq.linux.answer}
+{S.download.cliHint}{" "} {S.download.cliHintLink}