feat: add deb packaging and Gitea Actions automated release workflow
Build and Release / Build & Release DEB Package (push) Failing after 1m16s

This commit is contained in:
2026-09-23 01:42:52 +08:00
parent bde1a6b250
commit 30b7993d24
13 changed files with 3349 additions and 11 deletions
+52
View File
@@ -0,0 +1,52 @@
name: Build and Release
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag_name:
description: '发布版本标签 (如 v1.0.0)'
required: true
default: 'v1.0.0'
jobs:
release:
name: Build & Release DEB Package
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Install Linux build dependencies
run: |
sudo apt-get update || true
sudo apt-get install -y build-essential python3 libx11-dev libxext-dev libxrender-dev
- name: Install dependencies
run: |
npm ci || npm install
npm run rebuild
- name: Generate App Icons
run: |
python3 scripts/generate-icon.py
- name: Package Linux DEB
run: |
npm run package:deb
- name: Publish Release to Gitea
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_SERVER_URL: ${{ github.server_url }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_REF_NAME: ${{ github.event.inputs.tag_name || github.ref_name }}
run: |
node scripts/gitea-release.mjs
+52
View File
@@ -0,0 +1,52 @@
name: Build and Release
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag_name:
description: '发布版本标签 (如 v1.0.0)'
required: true
default: 'v1.0.0'
jobs:
release:
name: Build & Release DEB Package
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Install Linux build dependencies
run: |
sudo apt-get update || true
sudo apt-get install -y build-essential python3 libx11-dev libxext-dev libxrender-dev
- name: Install dependencies
run: |
npm ci || npm install
npm run rebuild
- name: Generate App Icons
run: |
python3 scripts/generate-icon.py
- name: Package Linux DEB
run: |
npm run package:deb
- name: Publish Release to Gitea
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_SERVER_URL: ${{ github.server_url }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_REF_NAME: ${{ github.event.inputs.tag_name || github.ref_name }}
run: |
node scripts/gitea-release.mjs
+14 -3
View File
@@ -42,16 +42,27 @@
--- ---
## 🚀 启动与构建 ## 🚀 启动与打包构建
```bash ```bash
# 启动开发终端 # 1. 启动开发终端
npm start npm start
# 若 Node ABI 变动,重新编译底层 node-pty # 2. 本地一键打包生成 Linux .deb 安装包 (生成于 dist/ 目录)
npm run package:deb
# 3. 安装生成的 deb 包
sudo dpkg -i dist/hatty_1.0.0_amd64.deb
# 4. 若 Node ABI 变动,重新编译底层 node-pty
npm run rebuild npm run rebuild
``` ```
### 🤖 Gitea Actions 自动化发版
项目已配置 Gitea Actions 流水线(`.gitea/workflows/release.yaml`):
- 推送以 `v` 开头的标签(如 `git tag v1.0.0 && git push origin v1.0.0`)会自动触发编译打包,并把 `.deb` 安装包作为附件发布到 Gitea Releases。
- 也可以在 Gitea 仓库页面中手动点击 **Actions -> Run workflow** 触发发布。
--- ---
## 🛠️ 架构分层 ## 🛠️ 架构分层
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

+3043 -3
View File
File diff suppressed because it is too large Load Diff
+38 -5
View File
@@ -5,11 +5,43 @@
"main": "src/main/index.cjs", "main": "src/main/index.cjs",
"scripts": { "scripts": {
"start": "electron .", "start": "electron .",
"rebuild": "electron-rebuild -f -w node-pty" "rebuild": "electron-rebuild -f -w node-pty",
"package:deb": "electron-builder --linux deb",
"package:all": "electron-builder --linux deb AppImage",
"dist": "electron-builder"
}, },
"keywords": [], "build": {
"author": "", "appId": "cloud.zzzjy.hatty",
"license": "ISC", "productName": "hatty",
"directories": {
"output": "dist",
"buildResources": "build"
},
"files": [
"src/**/*",
"node_modules/@xterm/**/*",
"node_modules/node-pty/**/*",
"package.json"
],
"linux": {
"target": [
"deb"
],
"category": "TerminalEmulator",
"maintainer": "zzzjy <zjy@zzzjy.cloud>",
"icon": "build/icons"
},
"deb": {
"packageName": "hatty",
"synopsis": "A modern, high-aesthetic Linux terminal emulator",
"description": "hatty is a fast, beautiful and modern terminal emulator for Linux built with Electron, WebGL and node-pty.",
"category": "Utility",
"priority": "optional"
}
},
"homepage": "https://git.zzzjy.cloud/zzzjy/hatty",
"author": "zzzjy <zjy@zzzjy.cloud>",
"license": "MIT",
"dependencies": { "dependencies": {
"@xterm/addon-fit": "^0.11.0", "@xterm/addon-fit": "^0.11.0",
"@xterm/addon-search": "^0.16.0", "@xterm/addon-search": "^0.16.0",
@@ -21,6 +53,7 @@
}, },
"devDependencies": { "devDependencies": {
"@electron/rebuild": "^4.2.0", "@electron/rebuild": "^4.2.0",
"electron": "^44.4.3" "electron": "^44.4.3",
"electron-builder": "^26.15.3"
} }
} }
+42
View File
@@ -0,0 +1,42 @@
import os
from PIL import Image, ImageDraw
os.makedirs('build/icons', exist_ok=True)
size = (512, 512)
img = Image.new('RGBA', size, (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
# 1. 绘制带有圆角的深色背景外框 (Tokyo Night 磨砂质感)
# 圆角矩形
draw.rounded_rectangle([(16, 16), (496, 496)], radius=100, fill=(26, 27, 38, 255), outline=(122, 162, 247, 180), width=6)
# 2. 内部顶部标题栏装饰小圆点 (macOS / Linux 风格微缩)
draw.ellipse([(60, 60), (76, 76)], fill=(247, 118, 142, 255)) # 红
draw.ellipse([(92, 60), (108, 76)], fill=(224, 175, 104, 255)) # 黄
draw.ellipse([(124, 60), (140, 76)], fill=(158, 206, 106, 255)) # 绿
# 3. 绘制终端提示符 > (青色发光)
points = [
(110, 180),
(240, 290),
(110, 400),
(150, 400),
(280, 290),
(150, 180),
]
draw.polygon(points, fill=(125, 207, 255, 255))
# 4. 绘制光标下划线 _ (品红霓虹)
draw.rectangle([(300, 360), (410, 395)], fill=(187, 154, 247, 255))
# 保存 512x512 图标
img.save('build/icon.png', 'PNG')
img.save('build/icons/512x512.png', 'PNG')
# 同时也生成常见的 256x256, 128x128, 64x64, 32x32 备用
for s in [256, 128, 64, 32]:
resized = img.resize((s, s), Image.Resampling.LANCZOS)
resized.save(f'build/icons/{s}x{s}.png', 'PNG')
print('App icons generated successfully!')
+108
View File
@@ -0,0 +1,108 @@
import fs from 'fs';
import path from 'path';
async function main() {
const serverUrl = process.env.GITHUB_SERVER_URL || process.env.GITEA_SERVER_URL || 'https://git.zzzjy.cloud';
const token = process.env.GITHUB_TOKEN || process.env.GITEA_TOKEN;
const repo = process.env.GITHUB_REPOSITORY || 'zzzjy/hatty';
const tag = process.env.GITHUB_REF_NAME || 'v1.0.0';
if (!token) {
console.error('Error: GITHUB_TOKEN or GITEA_TOKEN is required.');
process.exit(1);
}
const [owner, repoName] = repo.split('/');
const apiBase = `${serverUrl.replace(/\/+$/, '')}/api/v1/repos/${owner}/${repoName}`;
console.log(`Connecting to Gitea at ${serverUrl} for repository ${repo}...`);
// 1. 检查或创建 Release
let releaseId = null;
const tagCheckRes = await fetch(`${apiBase}/releases/tags/${tag}`, {
headers: {
Authorization: `token ${token}`,
Accept: 'application/json',
},
});
if (tagCheckRes.ok) {
const existing = await tagCheckRes.json();
releaseId = existing.id;
console.log(`Found existing release for tag ${tag} (id: ${releaseId})`);
} else {
console.log(`Creating new release for tag ${tag}...`);
const createRes = await fetch(`${apiBase}/releases`, {
method: 'POST',
headers: {
Authorization: `token ${token}`,
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify({
tag_name: tag,
name: `hatty ${tag}`,
body: `### ⚡ hatty ${tag} 现代化 Linux 终端新版本发布\n\n- **架构**:Electron + xterm.js (WebGL 硬件加速) + Linux PTY\n- **特性**:无边框磨砂毛玻璃、5 套现代暗黑主题、多标签页、灵活多窗格分屏\n- **安装说明**:下载下方附件 \`.deb\` 文件,在终端运行 \`sudo dpkg -i hatty_*.deb\` 即可完成安装。`,
draft: false,
prerelease: false,
}),
});
if (!createRes.ok) {
const errText = await createRes.text();
console.error(`Failed to create release: ${createRes.status} ${errText}`);
process.exit(1);
}
const created = await createRes.json();
releaseId = created.id;
console.log(`Release created successfully with id ${releaseId}`);
}
// 2. 查找 dist/ 下的 .deb 安装包
const distDir = path.resolve('dist');
if (!fs.existsSync(distDir)) {
console.error('dist directory does not exist.');
process.exit(1);
}
const files = fs.readdirSync(distDir).filter((f) => f.endsWith('.deb') || f.endsWith('.AppImage'));
if (files.length === 0) {
console.error('No .deb or .AppImage files found in dist directory.');
process.exit(1);
}
// 3. 上传安装包到 Release 附件
for (const fileName of files) {
const filePath = path.join(distDir, fileName);
const fileStat = fs.statSync(filePath);
console.log(`Uploading ${fileName} (${(fileStat.size / (1024 * 1024)).toFixed(2)} MB)...`);
const fileBuffer = fs.readFileSync(filePath);
const formData = new FormData();
const blob = new Blob([fileBuffer], { type: 'application/octet-stream' });
formData.append('attachment', blob, fileName);
const uploadRes = await fetch(`${apiBase}/releases/${releaseId}/assets?name=${encodeURIComponent(fileName)}`, {
method: 'POST',
headers: {
Authorization: `token ${token}`,
},
body: formData,
});
if (uploadRes.ok) {
console.log(`✔ Successfully uploaded ${fileName}`);
} else {
const err = await uploadRes.text();
console.warn(`Warning: Upload ${fileName} returned status ${uploadRes.status}: ${err}`);
}
}
console.log('All release operations completed successfully!');
}
main().catch((err) => {
console.error('Unexpected error:', err);
process.exit(1);
});