需求:让积分榜能在飞腾 D2000(ARM64)的 Linux 上跑起来,**同时不破坏 Windows**。
## 一、Go 服务端:交叉编译即可(**已实测**)
- SQLite 用的是 `modernc.org/sqlite`(纯 Go 驱动),全项目**没有一处 `import "C"`** → `CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build` 直接出;
- 产物是**静态链接**(`ELF 64-bit LSB executable, ARM aarch64, statically linked`)→ **不依赖 glibc 版本**,对国产系统友好;
- 本机(arm64)**实际启动验证**:数据目录、局域网地址探测、数据库迁移(0001/0002/0003)全部正常;
- 产物:`debug/服务端-arm64/scoreboard`(29 MB,`-s -w`)。
## 二、Console(Electron 宿主):平台差异收口到一个新文件
- **新增 `console/main/platform.js`**:平台差异集中在这 45 行里,其余代码不写平台判断。
⚠️ **`platform` 参数缺省按 Windows 解析** —— 漏传参数走的就是改动前那条老路(这比"改了再小心"更保守:**默认行为不变,新行为必须显式传参**)。
- `userpaths.js` / `datadir.js`:Windows 分支 `%LOCALAPPDATA%\ScoreboardConsole\` **逐字保留**;Linux 分支按 XDG(`$XDG_DATA_HOME`,缺省 `~/.local/share`)。两平台各自的"拿不到用户目录"提示文案分开写(免得现场去设一个本平台不存在的环境变量)。
- `config.js`:服务端文件名按平台取(Windows `scoreboard.exe` / Linux `scoreboard`)。
- **新增 `console/main/autostart.js`**:Linux 用 `~/.config/autostart/*.desktop`(开=写文件、关=删文件;Linux 没有"已注册但未启用"这个中间态)。
- 托盘与窗口行为:Linux 的通知区多数不派发 double-click,因此按平台分支成单击。
## 三、怎么保证没弄坏 Windows
1. **设计上**:平台差异集中 + 缺省值就是 Windows(见上);
2. **逐行核对 diff**:Windows 分支那几行原样未动;
3. **测试**:Console 由 **68 → 96 例**,新增全是平台分支用例,**既有断言一条没改**;
4. **真机验证**:构建机重打 Windows 包并跑(这一道才算收口)。
## 四、自检
全量自检两轮**全绿、`EXIT=0`**:服务端 14 包 / 前端 **944 例** / Console **96 例**。
## 版本
`dev_0.5.9` → **`dev_0.5.10`**;安装器素材同步重生成(照例只动 `brand.bmp` 两张)。
86 lines
3.7 KiB
JavaScript
86 lines
3.7 KiB
JavaScript
'use strict'
|
||
|
||
const fs = require('fs')
|
||
const path = require('path')
|
||
|
||
const { xdgConfigHome } = require('./platform')
|
||
|
||
// Linux 的「开机自启」只能靠 XDG 自启目录里的一个 .desktop:Electron 的
|
||
// setLoginItemSettings 在 Linux 上是空实现,桌面环境(GNOME / UKUI / 麒麟 / KDE)
|
||
// 读的都是 $XDG_CONFIG_HOME/autostart 或 ~/.config/autostart。
|
||
const AUTOSTART_FILE = 'scoreboard-console.desktop'
|
||
|
||
// 返回 .desktop 的绝对路径;非 Linux(Windows/macOS 走系统登录项)或拿不到 ~/.config 时
|
||
// 返回 null,由调用方回滚配置并记日志,绝不静默当成功。
|
||
function resolveAutostartFile({ platform = 'linux', env = process.env } = {}) {
|
||
if (platform !== 'linux') return null
|
||
const base = xdgConfigHome(env)
|
||
return base ? path.join(base, 'autostart', AUTOSTART_FILE) : null
|
||
}
|
||
|
||
// Exec 字段不是 shell 命令行,路径带空格必须自己加引号,否则自启时会被拆成两个参数、
|
||
// 找不到程序(现场装到「Program Files」那种带空格的目录下必然遇到)。
|
||
function quoteExec(exePath) {
|
||
return `"${String(exePath).replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`
|
||
}
|
||
|
||
function desktopEntry(exePath) {
|
||
return [
|
||
'[Desktop Entry]',
|
||
'Type=Application',
|
||
'Version=1.0',
|
||
'Name=Scoreboard Console',
|
||
'Name[zh_CN]=赛事积分榜',
|
||
'Comment=守护服务端并把大屏投到扩展屏',
|
||
`Exec=${quoteExec(exePath)}`,
|
||
'Terminal=false',
|
||
// 有些桌面环境只认这一个键;写 true 表明这份文件是「启用」状态(关掉是直接删文件)
|
||
'X-GNOME-Autostart-enabled=true',
|
||
'Hidden=false',
|
||
''
|
||
].join('\n')
|
||
}
|
||
|
||
// 「开」与「关」以文件实际状态为准(与 Windows 读登录项同一口径):
|
||
// 文件在就是开。Hidden=true 是 XDG 规范里「忽略这个文件」的写法 —— 现场从桌面环境的
|
||
// 「启动应用程序」面板里关掉自启时会这么标,所以必须认出来,否则界面会显示成还开着。
|
||
function isAutostartEnabled({ platform = 'linux', env = process.env } = {}) {
|
||
const file = resolveAutostartFile({ platform, env })
|
||
if (!file) return false
|
||
let text
|
||
try {
|
||
text = fs.readFileSync(file, 'utf8')
|
||
} catch {
|
||
return false
|
||
}
|
||
return !/^\s*Hidden\s*=\s*true\s*$/im.test(text)
|
||
}
|
||
|
||
// 开 → 写文件(覆盖旧的,每次启动 Console 按配置重写一遍是幂等的);
|
||
// 关 → 删文件。与 Windows 的「登录项一直保留、只切 enabled」不同:各桌面环境对
|
||
// X-GNOME-Autostart-enabled / Hidden 的支持不一致,写了标记却被忽略等于开关失灵,
|
||
// 删文件是唯一在各地都生效的做法(差异见回报)。
|
||
// 任何失败(含拿不到路径、平台不对)都抛出去,由调用方回滚配置,界面才不会说反。
|
||
function setAutostart({ platform = 'linux', env = process.env, exePath, enabled } = {}) {
|
||
const file = resolveAutostartFile({ platform, env })
|
||
if (!file) {
|
||
if (platform !== 'linux') throw new Error('开机自启在 Linux 上走 XDG 自启目录,本平台请用系统登录项')
|
||
throw new Error('拿不到 $XDG_CONFIG_HOME 与 $HOME,无法写自启文件')
|
||
}
|
||
|
||
if (enabled !== true) {
|
||
// force: 文件本来就不在(目录也不在)时不算失败
|
||
fs.rmSync(file, { force: true })
|
||
return { file }
|
||
}
|
||
|
||
if (typeof exePath !== 'string' || exePath === '') {
|
||
throw new Error('拿不到 Console 自身的可执行文件路径,无法写自启文件')
|
||
}
|
||
fs.mkdirSync(path.dirname(file), { recursive: true })
|
||
fs.writeFileSync(file, desktopEntry(exePath), 'utf8')
|
||
return { file }
|
||
}
|
||
|
||
module.exports = { AUTOSTART_FILE, resolveAutostartFile, isAutostartEnabled, setAutostart }
|