From 69f3c30fabce556319cd3f66c055adf722f55e83 Mon Sep 17 00:00:00 2001 From: mrlee <470349233@qq.com> Date: Fri, 2 Oct 2026 14:59:57 +0800 Subject: [PATCH] feat: scaffold native fnOS application stage 1 --- .gitignore | 5 ++ README.md | 38 ++++++++++ build.ps1 | 106 ++++++++++++++++++++++++++ havline/ICON.PNG | Bin 0 -> 3061 bytes havline/ICON_256.PNG | Bin 0 -> 13057 bytes havline/app/ui/config | 17 +++++ havline/app/ui/images/icon_256.png | Bin 0 -> 13057 bytes havline/app/ui/images/icon_64.png | Bin 0 -> 3061 bytes havline/cmd/config_callback | 12 +++ havline/cmd/config_init | 2 + havline/cmd/install_callback | 22 ++++++ havline/cmd/install_init | 2 + havline/cmd/main | 118 +++++++++++++++++++++++++++++ havline/cmd/uninstall_callback | 9 +++ havline/cmd/uninstall_init | 2 + havline/cmd/upgrade_callback | 14 ++++ havline/cmd/upgrade_init | 2 + havline/config/privilege | 7 ++ havline/config/resource | 1 + havline/manifest | 14 ++++ havline/wizard/config | 17 +++++ havline/wizard/install | 35 +++++++++ havline/wizard/uninstall | 21 +++++ 23 files changed, 444 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 build.ps1 create mode 100644 havline/ICON.PNG create mode 100644 havline/ICON_256.PNG create mode 100644 havline/app/ui/config create mode 100644 havline/app/ui/images/icon_256.png create mode 100644 havline/app/ui/images/icon_64.png create mode 100644 havline/cmd/config_callback create mode 100644 havline/cmd/config_init create mode 100644 havline/cmd/install_callback create mode 100644 havline/cmd/install_init create mode 100644 havline/cmd/main create mode 100644 havline/cmd/uninstall_callback create mode 100644 havline/cmd/uninstall_init create mode 100644 havline/cmd/upgrade_callback create mode 100644 havline/cmd/upgrade_init create mode 100644 havline/config/privilege create mode 100644 havline/config/resource create mode 100644 havline/manifest create mode 100644 havline/wizard/config create mode 100644 havline/wizard/install create mode 100644 havline/wizard/uninstall diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f91f5d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.build/ +dist/ +*.fpk +havline/app/server/amd64/havline +havline/app/server/arm64/havline diff --git a/README.md b/README.md new file mode 100644 index 0000000..22bdd37 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# Havline Native fnOS 应用 + +本目录保存 Havline 的飞牛 Native 应用工程,不依赖 Docker。 + +阶段一范围: + +- Go 主程序原生运行 +- 统一网关 `/app/havline` +- Unix Socket `app.sock` +- `cmd/main` 启动、停止和状态检查 +- amd64 与 arm64 双架构二进制 + +当前不包含: + +- Nginx 二进制 +- frpc +- cloudflared + +这些依赖将在后续阶段加入。 + +## 构建 + +```powershell +cd F:\Fonu-main\fnos-native +.\build.ps1 +``` + +输出: + +```text +dist/havline-native-1.9.0.fpk +``` + +构建脚本会: + +1. 使用 `VITE_BASE_PATH=/app/havline/` 构建前端; +2. 交叉编译 amd64 与 arm64 Go 二进制; +3. 调用 `fnpack build` 打包。 diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..b5ec156 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,106 @@ +param( + [string]$FnpackPath = "", + [string]$GoBin = "go" +) + +$ErrorActionPreference = "Stop" +$root = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path +$project = Join-Path $PSScriptRoot "havline" +$mainRepo = Join-Path $root "havline" +$version = (Get-Content -Raw (Join-Path $mainRepo "VERSION")).Trim() + +function Copy-Tree { + param( + [string]$Source, + [string]$Destination + ) + + New-Item -ItemType Directory -Force $Destination | Out-Null + robocopy $Source $Destination /E /NFL /NDL /NJH /NJS /NP | Out-Null + if ($LASTEXITCODE -ge 8) { + throw "复制失败:$Source -> $Destination,退出码:$LASTEXITCODE" + } +} + +if (-not (Test-Path -LiteralPath $project)) { + throw "找不到 Native 应用工程:$project" +} + +if (-not $FnpackPath) { + $localTool = Join-Path $root "fnos\fnpack.exe" + $FnpackPath = if (Test-Path -LiteralPath $localTool) { $localTool } else { "fnpack" } +} + +Push-Location (Join-Path $mainRepo "web") +try { + $env:VITE_BASE_PATH = "/app/havline/" + npm run build + if ($LASTEXITCODE -ne 0) { + throw "前端构建失败,退出码:$LASTEXITCODE" + } +} +finally { + Pop-Location +} + +$webDist = Join-Path $mainRepo "web\dist" +$embedDist = Join-Path $mainRepo "cmd\havline\web\dist" +New-Item -ItemType Directory -Force $embedDist | Out-Null +robocopy $webDist $embedDist /E /NFL /NDL /NJH /NJS /NP | Out-Null +if ($LASTEXITCODE -ge 8) { + throw "复制前端产物失败,robocopy 退出码:$LASTEXITCODE" +} + +$stamp = Get-Date -Format "yyyyMMddHHmmss" +$stage = Join-Path $PSScriptRoot ".build\havline-$stamp" +New-Item -ItemType Directory -Force $stage | Out-Null +robocopy $project $stage /E /XF .DS_Store /NFL /NDL /NJH /NJS /NP | Out-Null +if ($LASTEXITCODE -ge 8) { + throw "复制 Native 应用工程失败,robocopy 退出码:$LASTEXITCODE" +} + +Copy-Tree -Source (Join-Path $mainRepo "migrations") -Destination (Join-Path $stage "app\migrations") +Copy-Tree -Source (Join-Path $mainRepo "web\assets\image") -Destination (Join-Path $stage "app\web\assets\image") + +$env:CGO_ENABLED = "0" +$env:GOOS = "linux" + +foreach ($arch in @("amd64", "arm64")) { + $outputDir = Join-Path $stage "app\server\$arch" + New-Item -ItemType Directory -Force $outputDir | Out-Null + $env:GOARCH = $arch + + Push-Location $mainRepo + try { + & $GoBin build -trimpath ` + -ldflags "-X github.com/havline/havline/internal/version.Version=$version" ` + -o (Join-Path $outputDir "havline") ` + ./cmd/havline + if ($LASTEXITCODE -ne 0) { + throw "编译 $arch 二进制失败,退出码:$LASTEXITCODE" + } + } + finally { + Pop-Location + } +} + +Push-Location $PSScriptRoot +try { + & $FnpackPath build -d $stage + if ($LASTEXITCODE -ne 0) { + throw "fnpack build 失败,退出码:$LASTEXITCODE" + } + + $output = Join-Path $PSScriptRoot "havline.fpk" + $dist = Join-Path $PSScriptRoot "dist" + New-Item -ItemType Directory -Force $dist | Out-Null + $target = Join-Path $dist "havline-native-$version.fpk" + Copy-Item -LiteralPath $output -Destination $target -Force + $sizeMB = [math]::Round((Get-Item $target).Length / 1MB, 1) + Write-Output "FPK: $target" + Write-Output "FPK 大小:$sizeMB MB" +} +finally { + Pop-Location +} diff --git a/havline/ICON.PNG b/havline/ICON.PNG new file mode 100644 index 0000000000000000000000000000000000000000..99aae3060c167c6e6d16e8f81452efadd2e25e95 GIT binary patch literal 3061 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 D-ErK1 literal 0 HcmV?d00001 diff --git a/havline/ICON_256.PNG b/havline/ICON_256.PNG new file mode 100644 index 0000000000000000000000000000000000000000..c8533dfc83383b0780b38933fc5eb823e58f489e GIT binary patch literal 13057 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 D-ErK1 literal 0 HcmV?d00001 diff --git a/havline/cmd/config_callback b/havline/cmd/config_callback new file mode 100644 index 0000000..db644a3 --- /dev/null +++ b/havline/cmd/config_callback @@ -0,0 +1,12 @@ +#!/bin/bash +set -euo pipefail + +for var in wizard_nginx_http_port wizard_nginx_https_port; do + value="${!var:-}" + if [ -n "$value" ] && ! [[ "$value" =~ ^[0-9]+$ ]]; then + echo "$var 必须是数字端口" > "$TRIM_TEMP_LOGFILE" + exit 1 + fi +done + +exit 0 diff --git a/havline/cmd/config_init b/havline/cmd/config_init new file mode 100644 index 0000000..06bd986 --- /dev/null +++ b/havline/cmd/config_init @@ -0,0 +1,2 @@ +#!/bin/bash +exit 0 diff --git a/havline/cmd/install_callback b/havline/cmd/install_callback new file mode 100644 index 0000000..7774e12 --- /dev/null +++ b/havline/cmd/install_callback @@ -0,0 +1,22 @@ +#!/bin/bash +set -euo pipefail + +mkdir -p "$TRIM_PKGVAR/logs" +chown -R "$TRIM_USERNAME:$TRIM_GROUPNAME" "$TRIM_PKGVAR" + +ARCH="$(uname -m)" +case "$ARCH" in + x86_64|amd64) ARCH_DIR="amd64" ;; + aarch64|arm64) ARCH_DIR="arm64" ;; + *) echo "不支持的设备架构:$ARCH" > "$TRIM_TEMP_LOGFILE"; exit 1 ;; +esac + +BIN="$TRIM_APPDEST/server/$ARCH_DIR/havline" +if [ ! -f "$BIN" ]; then + echo "找不到原生二进制:$BIN" > "$TRIM_TEMP_LOGFILE" + exit 1 +fi + +chmod +x "$BIN" +echo "Havline 数据目录已准备:$TRIM_PKGVAR" +exit 0 diff --git a/havline/cmd/install_init b/havline/cmd/install_init new file mode 100644 index 0000000..06bd986 --- /dev/null +++ b/havline/cmd/install_init @@ -0,0 +1,2 @@ +#!/bin/bash +exit 0 diff --git a/havline/cmd/main b/havline/cmd/main new file mode 100644 index 0000000..f9b373b --- /dev/null +++ b/havline/cmd/main @@ -0,0 +1,118 @@ +#!/bin/bash +set -u + +PID_FILE="${TRIM_PKGVAR}/havline.pid" +LOG_FILE="${TRIM_PKGVAR}/logs/app.log" +SOCKET_PATH="${TRIM_APPDEST}/app.sock" + +mkdir -p "${TRIM_PKGVAR}/logs" + +log_msg() { + echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE" +} + +check_process() { + local pid="$1" + kill -0 "$pid" 2>/dev/null +} + +status_app() { + if [ -f "$PID_FILE" ]; then + local pid + pid="$(head -n 1 "$PID_FILE" | tr -d '[:space:]')" + if [ -n "$pid" ] && check_process "$pid"; then + return 0 + fi + rm -f "$PID_FILE" + fi + return 1 +} + +arch_dir() { + case "$(uname -m)" in + x86_64|amd64) echo "amd64" ;; + aarch64|arm64) echo "arm64" ;; + *) return 1 ;; + esac +} + +start_app() { + if status_app; then + return 0 + fi + + local arch + arch="$(arch_dir)" || { + echo "不支持的设备架构:$(uname -m)" > "$TRIM_TEMP_LOGFILE" + exit 1 + } + + local bin="${TRIM_APPDEST}/server/${arch}/havline" + if [ ! -x "$bin" ]; then + echo "找不到原生二进制:$bin" > "$TRIM_TEMP_LOGFILE" + exit 1 + fi + + export HAVLINE_DATA_DIR="$TRIM_PKGVAR" + export HAVLINE_LISTEN="127.0.0.1:6893" + export HAVLINE_GATEWAY_SOCKET="$SOCKET_PATH" + export HAVLINE_GATEWAY_PREFIX="/app/havline" + export HAVLINE_UPDATER_SOCKET="" + export HAVLINE_NGINX_HTTP_PORT="${wizard_nginx_http_port:-18080}" + export HAVLINE_NGINX_HTTPS_PORT="${wizard_nginx_https_port:-9443}" + export TZ="${wizard_timezone:-Asia/Shanghai}" + + if [ -n "${wizard_initial_admin_password:-}" ]; then + export HAVLINE_INITIAL_ADMIN_PASSWORD="$wizard_initial_admin_password" + fi + + rm -f "$SOCKET_PATH" + log_msg "Starting Havline native" + cd "$TRIM_APPDEST" || exit 1 + nohup "$bin" >> "$LOG_FILE" 2>&1 & + echo "$!" > "$PID_FILE" + sleep 1 + status_app +} + +stop_app() { + if [ ! -f "$PID_FILE" ]; then + rm -f "$SOCKET_PATH" + return 0 + fi + + local pid + pid="$(head -n 1 "$PID_FILE" | tr -d '[:space:]')" + if [ -n "$pid" ] && check_process "$pid"; then + kill -TERM "$pid" 2>> "$LOG_FILE" || true + local count=0 + while check_process "$pid" && [ "$count" -lt 15 ]; do + sleep 1 + count=$((count + 1)) + done + if check_process "$pid"; then + kill -KILL "$pid" 2>> "$LOG_FILE" || true + fi + fi + + rm -f "$PID_FILE" "$SOCKET_PATH" +} + +case "${1:-}" in + start) + start_app + ;; + stop) + stop_app + ;; + status) + if status_app; then + exit 0 + fi + exit 3 + ;; + *) + echo "Unknown command: ${1:-}" > "$TRIM_TEMP_LOGFILE" + exit 1 + ;; +esac diff --git a/havline/cmd/uninstall_callback b/havline/cmd/uninstall_callback new file mode 100644 index 0000000..0e8c309 --- /dev/null +++ b/havline/cmd/uninstall_callback @@ -0,0 +1,9 @@ +#!/bin/bash +set -u + +if [ "${wizard_uninstall_mode:-keep}" = "purge" ]; then + rm -rf "$TRIM_PKGVAR" +fi + +rm -f "$TRIM_APPDEST/app.sock" +exit 0 diff --git a/havline/cmd/uninstall_init b/havline/cmd/uninstall_init new file mode 100644 index 0000000..06bd986 --- /dev/null +++ b/havline/cmd/uninstall_init @@ -0,0 +1,2 @@ +#!/bin/bash +exit 0 diff --git a/havline/cmd/upgrade_callback b/havline/cmd/upgrade_callback new file mode 100644 index 0000000..c22a518 --- /dev/null +++ b/havline/cmd/upgrade_callback @@ -0,0 +1,14 @@ +#!/bin/bash +set -euo pipefail + +if [ -f "$TRIM_PKGVAR/havline.db" ] && [ ! -r "$TRIM_PKGVAR/havline.db" ]; then + echo "数据库不可读:$TRIM_PKGVAR/havline.db" > "$TRIM_TEMP_LOGFILE" + exit 1 +fi + +if [ -f "$TRIM_PKGVAR/session_secret" ] && [ ! -r "$TRIM_PKGVAR/session_secret" ]; then + echo "会话密钥不可读:$TRIM_PKGVAR/session_secret" > "$TRIM_TEMP_LOGFILE" + exit 1 +fi + +exit 0 diff --git a/havline/cmd/upgrade_init b/havline/cmd/upgrade_init new file mode 100644 index 0000000..06bd986 --- /dev/null +++ b/havline/cmd/upgrade_init @@ -0,0 +1,2 @@ +#!/bin/bash +exit 0 diff --git a/havline/config/privilege b/havline/config/privilege new file mode 100644 index 0000000..cff6a3a --- /dev/null +++ b/havline/config/privilege @@ -0,0 +1,7 @@ +{ + "defaults": { + "run-as": "package" + }, + "username": "havline", + "groupname": "havline" +} diff --git a/havline/config/resource b/havline/config/resource new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/havline/config/resource @@ -0,0 +1 @@ +{} diff --git a/havline/manifest b/havline/manifest new file mode 100644 index 0000000..2aec020 --- /dev/null +++ b/havline/manifest @@ -0,0 +1,14 @@ +appname = havline +version = 1.9.0 +display_name = Havline +desc = NAS 公网访问管理:原生应用阶段一 +platform = all +source = thirdparty +maintainer = Havline +maintainer_url = https://jayhub.top/mrlee/Havline +distributor = Havline +desktop_uidir = ui +desktop_applaunchname = havline.main +ctl_stop = true +os_min_version = 1.1.3100 +changelog = 原生应用阶段一:Go 主程序、统一网关和启停脚本 diff --git a/havline/wizard/config b/havline/wizard/config new file mode 100644 index 0000000..03a5a08 --- /dev/null +++ b/havline/wizard/config @@ -0,0 +1,17 @@ +[ + { + "stepTitle": "基础设置", + "items": [ + { + "type": "select", + "field": "wizard_timezone", + "label": "时区", + "initValue": "Asia/Shanghai", + "options": [ + { "label": "Asia/Shanghai", "value": "Asia/Shanghai" }, + { "label": "UTC", "value": "UTC" } + ] + } + ] + } +] diff --git a/havline/wizard/install b/havline/wizard/install new file mode 100644 index 0000000..f0f3fca --- /dev/null +++ b/havline/wizard/install @@ -0,0 +1,35 @@ +[ + { + "stepTitle": "管理员初始化", + "items": [ + { + "type": "password", + "field": "wizard_initial_admin_password", + "label": "初始管理员密码", + "rules": [ + { "required": true, "message": "请输入初始管理员密码" }, + { "min": 8, "message": "密码至少 8 位" } + ] + }, + { + "type": "tips", + "helpText": "首次启动时使用该密码创建 admin 管理员。登录后请在系统设置中修改密码。" + } + ] + }, + { + "stepTitle": "基础设置", + "items": [ + { + "type": "select", + "field": "wizard_timezone", + "label": "时区", + "initValue": "Asia/Shanghai", + "options": [ + { "label": "Asia/Shanghai", "value": "Asia/Shanghai" }, + { "label": "UTC", "value": "UTC" } + ] + } + ] + } +] diff --git a/havline/wizard/uninstall b/havline/wizard/uninstall new file mode 100644 index 0000000..3545dd5 --- /dev/null +++ b/havline/wizard/uninstall @@ -0,0 +1,21 @@ +[ + { + "stepTitle": "卸载选项", + "items": [ + { + "type": "radio", + "field": "wizard_uninstall_mode", + "label": "数据处理", + "initValue": "keep", + "options": [ + { "label": "保留数据(推荐)", "value": "keep" }, + { "label": "删除全部数据", "value": "purge" } + ] + }, + { + "type": "tips", + "helpText": "删除数据将移除数据库、证书、Nginx 配置、FRP 与 Cloudflare 配置,且无法恢复。" + } + ] + } +]