feat: rename project from Fonu to Havline
CI / verify (push) Canceled after 0s

This commit is contained in:
2026-10-01 16:54:35 +08:00
parent c377405696
commit 8fa5d922ac
250 changed files with 6724 additions and 5070 deletions
+18 -18
View File
@@ -6,13 +6,13 @@ if [ "$(id -u)" -ne 0 ]; then
exit 1
fi
AGENT_BINARY=${1:-fonu-agent}
PREFIX=/usr/local/bin/fonu-agent
CONF_DIR=/etc/fonu-agent
AGENT_BINARY=${1:-havline-agent}
PREFIX=/usr/local/bin/havline-agent
CONF_DIR=/etc/havline-agent
CONF_FILE="$CONF_DIR/agent.env"
UNIT=/etc/systemd/system/fonu-agent.service
mkdir -p "$CONF_DIR" /var/lib/fonu-agent/certs
systemctl stop fonu-agent 2>/dev/null || true
UNIT=/etc/systemd/system/havline-agent.service
mkdir -p "$CONF_DIR" /var/lib/havline-agent/certs
systemctl stop havline-agent 2>/dev/null || true
TMP_PREFIX="${PREFIX}.new"
# 迁移旧版 Agent 留在 nginx include 范围内的 <配置>.versions 目录(保留历史版本内容)
for NGINX_DIR in /etc/nginx/sites-enabled /etc/nginx/conf.d; do
@@ -42,26 +42,26 @@ printf 'agent version: %s\n' "$AGENT_VERSION"
if [ ! -f "$CONF_FILE" ]; then
umask 077
# 用 /dev/urandom 生成 64 位 hex token,不依赖 openssl(与 Fonu 内嵌副本保持同一份逻辑)
printf 'FONU_AGENT_TOKEN=%s\n' "$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')" > "$CONF_FILE"
# 用 /dev/urandom 生成 64 位 hex token,不依赖 openssl(与 Havline 内嵌副本保持同一份逻辑)
printf 'HAVLINE_AGENT_TOKEN=%s\n' "$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')" > "$CONF_FILE"
fi
# 默认只监听回环;显式删除旧公网监听,由中央 Fonu 通过 SSH 隧道访问。
# 默认只监听回环;显式删除旧公网监听,由中央 Havline 通过 SSH 隧道访问。
cp "$CONF_FILE" "$CONF_FILE.previous" 2>/dev/null || true
touch "$CONF_FILE"
sed -i '/^FONU_AGENT_LISTEN=/d; /^FONU_AGENT_ALLOW_REMOTE=/d' "$CONF_FILE"
printf 'FONU_AGENT_LISTEN=127.0.0.1:7700\nFONU_AGENT_ALLOW_REMOTE=0\n' >> "$CONF_FILE"
sed -i '/^HAVLINE_AGENT_LISTEN=/d; /^HAVLINE_AGENT_ALLOW_REMOTE=/d' "$CONF_FILE"
printf 'HAVLINE_AGENT_LISTEN=127.0.0.1:7700\nHAVLINE_AGENT_ALLOW_REMOTE=0\n' >> "$CONF_FILE"
cat > "$UNIT" <<'UNIT'
[Unit]
Description=Fonu FRPS management agent
Description=Havline FRPS management agent
After=network-online.target nginx.service
Wants=network-online.target
[Service]
Type=simple
EnvironmentFile=/etc/fonu-agent/agent.env
ExecStart=/usr/local/bin/fonu-agent
EnvironmentFile=/etc/havline-agent/agent.env
ExecStart=/usr/local/bin/havline-agent
Restart=on-failure
RestartSec=3
User=root
@@ -71,9 +71,9 @@ WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl restart fonu-agent
systemctl enable fonu-agent
TOKEN=$(sed -n 's/^FONU_AGENT_TOKEN=//p' "$CONF_FILE")
echo "fonu-agent 已安装并启动"
systemctl restart havline-agent
systemctl enable havline-agent
TOKEN=$(sed -n 's/^HAVLINE_AGENT_TOKEN=//p' "$CONF_FILE")
echo "havline-agent 已安装并启动"
echo "API: SSH tunnel -> 127.0.0.1:7700"
echo "Token: $TOKEN"
+16 -16
View File
@@ -1,11 +1,11 @@
<#
<#
.SYNOPSIS
构建 Fonu 的 Linux 安装包(tar.gz:二进制 + migrations + systemd 安装脚本)。
构建 Havline 的 Linux 安装包(tar.gz:二进制 + migrations + systemd 安装脚本)。
.DESCRIPTION
与 Dockerfile 同源的三步:
1) 前端构建(VITE_APP_VERSION 取根目录 VERSION,dist 会被 //go:embed 进二进制)
2) 交叉编译 fonu-agent 双架构到 internal/frp/agentbin(主程序构建时内嵌)
2) 交叉编译 havline-agent 双架构到 internal/frp/agentbin(主程序构建时内嵌)
3) CGO_ENABLED=0 交叉编译主程序并打包
依赖是纯 Go 的(modernc.org/sqlite),所以 Windows 上就能产出 Linux 包,不需要 Docker/WSL。
@@ -27,7 +27,7 @@ Set-Location $root
$version = (Get-Content (Join-Path $root 'VERSION') -Raw).Trim()
if (-not $version) { throw 'VERSION 文件为空,无法确定版本号' }
Write-Host "==> Fonu $version" -ForegroundColor Cyan
Write-Host "==> Havline $version" -ForegroundColor Cyan
# ---- 1) 前端:dist 缺失会让主程序编译失败(//go:embed all:web/dist)
if (-not $SkipWeb) {
@@ -46,19 +46,19 @@ if (-not $SkipWeb) {
Remove-Item Env:VITE_APP_VERSION -ErrorAction SilentlyContinue
}
}
if (-not (Test-Path (Join-Path $root 'cmd/fonu/web/dist/index.html'))) {
throw '缺少 cmd/fonu/web/dist/index.html:请先构建前端(或去掉 -SkipWeb)'
if (-not (Test-Path (Join-Path $root 'cmd/havline/web/dist/index.html'))) {
throw '缺少 cmd/havline/web/dist/index.html:请先构建前端(或去掉 -SkipWeb)'
}
# ---- 2) fonu-agent 双架构:主程序会内嵌这两个文件,必须在构建主程序前就位
# ---- 2) havline-agent 双架构:主程序会内嵌这两个文件,必须在构建主程序前就位
if (-not $SkipAgent) {
Write-Host '==> 交叉编译 fonu-agent(amd64/arm64)'
Write-Host '==> 交叉编译 havline-agent(amd64/arm64)'
$env:CGO_ENABLED = '0'
$env:GOOS = 'linux'
foreach ($a in @('amd64', 'arm64')) {
$env:GOARCH = $a
go build -trimpath -o "internal/frp/agentbin/fonu-agent-linux-$a" ./cmd/fonu-agent
if ($LASTEXITCODE -ne 0) { throw "fonu-agent($a) 构建失败(exit $LASTEXITCODE)" }
go build -trimpath -o "internal/frp/agentbin/havline-agent-linux-$a" ./cmd/havline-agent
if ($LASTEXITCODE -ne 0) { throw "havline-agent($a) 构建失败(exit $LASTEXITCODE)" }
}
}
@@ -69,23 +69,23 @@ New-Item -ItemType Directory -Force -Path $outPath | Out-Null
try {
foreach ($a in $archs) {
$name = "fonu-$version-linux-$a"
$name = "havline-$version-linux-$a"
$stage = Join-Path $outPath $name
if (Test-Path $stage) { Remove-Item -Recurse -Force $stage }
New-Item -ItemType Directory -Force -Path (Join-Path $stage 'migrations') | Out-Null
Write-Host "==> 交叉编译 fonu(linux/$a)"
Write-Host "==> 交叉编译 havline(linux/$a)"
$env:CGO_ENABLED = '0'
$env:GOOS = 'linux'
$env:GOARCH = $a
go build -trimpath `
-ldflags "-s -w -X github.com/fonu/fonu/internal/version.Version=$version" `
-o (Join-Path $stage 'fonu') ./cmd/fonu
if ($LASTEXITCODE -ne 0) { throw "fonu($a) 构建失败(exit $LASTEXITCODE)" }
-ldflags "-s -w -X github.com/havline/havline/internal/version.Version=$version" `
-o (Join-Path $stage 'havline') ./cmd/havline
if ($LASTEXITCODE -ne 0) { throw "havline($a) 构建失败(exit $LASTEXITCODE)" }
# 包内容与 Dockerfile 的运行时 stage 对齐:migrations 必须与二进制同级(程序按工作目录找)
Copy-Item (Join-Path $root 'migrations/*.sql') (Join-Path $stage 'migrations') -Force
Copy-Item (Join-Path $root 'scripts/fonu-install.sh') (Join-Path $stage 'install.sh') -Force
Copy-Item (Join-Path $root 'scripts/havline-install.sh') (Join-Path $stage 'install.sh') -Force
Copy-Item (Join-Path $root 'VERSION') $stage -Force
Copy-Item (Join-Path $root 'LICENSE') $stage -Force
if (Test-Path (Join-Path $root 'web/assets/image')) {
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Fonu Linux 安装脚本(随 tar.gz 一起分发)
# Havline Linux 安装脚本(随 tar.gz 一起分发)
#
# sudo bash ./install.sh 安装并启动
# sudo bash ./install.sh --port 8080 换监听端口
@@ -9,24 +9,24 @@
# 用 `bash ./install.sh` 而不是 `./install.sh`:Windows 上打出来的 tar 不带执行位
# (本机 tar 不支持 --mode,待装本时无法保留 +x),显式调 bash 就不需要先 chmod。
#
# 关于初始口令:Fonu 首次启动会随机生成一个 16 位管理员口令(用户名 admin),
# 关于初始口令:Havline 首次启动会随机生成一个 16 位管理员口令(用户名 admin),
# 打进攻日志与日志文件(<数据目录>/logs/app.log),本脚本装完会直接把它打印出来。
# 忘了 / 想重新初始化:sudo bash ./install.sh --reset-admin
#
# 安装布局(与 Docker 版同一套环境变量,见 Dockerfile 的 ENV 块):
# /opt/fonu/fonu 主程序
# /opt/fonu/migrations/*.sql 迁移脚本(程序按工作目录找它,必须与二进制同级)
# /var/lib/fonu/ 数据目录(库、日志、证书、nginx 与 frp 的运行数据)
# /etc/fonu/fonu.env 环境变量(含随机生成的会话密钥,0600)
# /etc/systemd/system/fonu.service
# /opt/havline/havline 主程序
# /opt/havline/migrations/*.sql 迁移脚本(程序按工作目录找它,必须与二进制同级)
# /var/lib/havline/ 数据目录(库、日志、证书、nginx 与 frp 的运行数据)
# /etc/havline/havline.env 环境变量(含随机生成的会话密钥,0600)
# /etc/systemd/system/havline.service
set -euo pipefail
PREFIX=/opt/fonu
DATA_DIR=/var/lib/fonu
ENV_DIR=/etc/fonu
ENV_FILE=$ENV_DIR/fonu.env
UNIT=/etc/systemd/system/fonu.service
SERVICE_NAME=fonu
PREFIX=/opt/havline
DATA_DIR=/var/lib/havline
ENV_DIR=/etc/havline
ENV_FILE=$ENV_DIR/havline.env
UNIT=/etc/systemd/system/havline.service
SERVICE_NAME=havline
PORT=6893
UNINSTALL=0
PURGE=0
@@ -91,17 +91,17 @@ print_initial_password() {
# 重置管理员:删掉 admins 行后重启,启动流程会重新生成一个随机口令并写进日志
reset_admin() {
if [ ! -f "$DATA_DIR/fonu.db" ]; then
echo "找不到数据库 $DATA_DIR/fonu.db,无需重置" >&2
if [ ! -f "$DATA_DIR/havline.db" ]; then
echo "找不到数据库 $DATA_DIR/havline.db,无需重置" >&2
exit 1
fi
systemctl stop "$SERVICE_NAME" 2>/dev/null || true
local backup="$DATA_DIR/fonu.db.bak.$(date +%Y%m%d-%H%M%S)"
cp "$DATA_DIR/fonu.db" "$backup"
local backup="$DATA_DIR/havline.db.bak.$(date +%Y%m%d-%H%M%S)"
cp "$DATA_DIR/havline.db" "$backup"
if command -v sqlite3 >/dev/null 2>&1; then
sqlite3 "$DATA_DIR/fonu.db" 'DELETE FROM sessions; DELETE FROM admins;'
sqlite3 "$DATA_DIR/havline.db" 'DELETE FROM sessions; DELETE FROM admins;'
elif command -v python3 >/dev/null 2>&1; then
python3 - "$DATA_DIR/fonu.db" <<'PY'
python3 - "$DATA_DIR/havline.db" <<'PY'
import sqlite3, sys
conn = sqlite3.connect(sys.argv[1])
conn.execute('DELETE FROM sessions')
@@ -130,7 +130,7 @@ if [ "$RESET_ADMIN" -eq 1 ]; then
reset_admin
fi
for f in fonu migrations; do
for f in havline migrations; do
if [ ! -e "$SCRIPT_DIR/$f" ]; then
echo "安装包不完整:缺少 $f(请在解压后的目录里运行 ./install.sh)" >&2
exit 1
@@ -164,7 +164,7 @@ install -d -m 0750 "$DATA_DIR"
install -d -m 0750 "$ENV_DIR"
install -d -m 0755 "$PREFIX/migrations"
install -m 0755 "$SCRIPT_DIR/fonu" "$PREFIX/fonu"
install -m 0755 "$SCRIPT_DIR/havline" "$PREFIX/havline"
rm -f "$PREFIX"/migrations/*.sql
cp "$SCRIPT_DIR"/migrations/*.sql "$PREFIX/migrations/"
install -m 0644 "$SCRIPT_DIR/VERSION" "$PREFIX/VERSION" 2>/dev/null || true
@@ -173,11 +173,11 @@ install -m 0644 "$SCRIPT_DIR/VERSION" "$PREFIX/VERSION" 2>/dev/null || true
if [ ! -f "$ENV_FILE" ]; then
SECRET=$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')
cat > "$ENV_FILE" <<EOF
# Fonu 环境变量(由 install.sh 生成)。修改后:systemctl restart $SERVICE_NAME
FONU_DATA_DIR=$DATA_DIR
FONU_LISTEN=:$PORT
FONU_SESSION_SECRET=$SECRET
FONU_NGINX_MIME_TYPES=$MIME_TYPES
# Havline 环境变量(由 install.sh 生成)。修改后:systemctl restart $SERVICE_NAME
HAVLINE_DATA_DIR=$DATA_DIR
HAVLINE_LISTEN=:$PORT
HAVLINE_SESSION_SECRET=$SECRET
HAVLINE_NGINX_MIME_TYPES=$MIME_TYPES
TZ=${TZ:-Asia/Shanghai}
EOF
chmod 0600 "$ENV_FILE"
@@ -188,18 +188,18 @@ fi
cat > "$UNIT" <<EOF
[Unit]
Description=Fonu 自托管网关管理面板
Documentation=https://github.com/fonu/fonu
Description=Havline 自托管网关管理面板
Documentation=https://github.com/havline/havline
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
# Fonu 会管理同机的 nginx(改配置 / reload / 绑 80·443)并拉起 frpc,需要相应权限
# Havline 会管理同机的 nginx(改配置 / reload / 绑 80·443)并拉起 frpc,需要相应权限
User=root
WorkingDirectory=$PREFIX
EnvironmentFile=$ENV_FILE
ExecStart=$PREFIX/fonu
ExecStart=$PREFIX/havline
Restart=on-failure
RestartSec=3
# 日志进 journald:journalctl -u $SERVICE_NAME -f
+9 -9
View File
@@ -1,11 +1,11 @@
#!/usr/bin/env bash
# Fonu 一键升级(适用于「只有编排文件」的部署目录,即 docker-compose.hub.yml 那种)
# Havline 一键升级(适用于「只有编排文件」的部署目录,即 docker-compose.hub.yml 那种)
#
# sudo bash upgrade-hub.sh
#
# 可选环境变量:
# COMPOSE_FILE=docker-compose.hub.yml # 要用的编排文件
# REPO=http://.../mrlee/-Fonu.git # 源码仓库
# REPO=http://.../mrlee/Havline.git # 源码仓库
# BRANCH=main # 分支
#
# 它做四件事:
@@ -16,14 +16,14 @@
# 4) 打印「回滚到上一个 tag」的命令
set -euo pipefail
REPO="${REPO:-http://45.207.223.190:10082/mrlee/-Fonu.git}"
REPO="${REPO:-http://45.207.223.190:10082/mrlee/Havline.git}"
BRANCH="${BRANCH:-main}"
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.hub.yml}"
APP_CONTAINER="${APP_CONTAINER:-fonu}"
APP_CONTAINER="${APP_CONTAINER:-havline}"
HEALTH_URL="${HEALTH_URL:-http://127.0.0.1:6893/api/version}"
if ! command -v docker >/dev/null 2>&1; then
echo "没有找到 docker:这个脚本要在运行 Fonu 容器的那台机器上执行" >&2
echo "没有找到 docker:这个脚本要在运行 Havline 容器的那台机器上执行" >&2
exit 1
fi
if [ ! -f "$COMPOSE_FILE" ]; then
@@ -50,8 +50,8 @@ if [ -z "$version" ]; then
echo "==> 未能从仓库读到 VERSION(可能没装 git),改用时间戳 tag:$version"
fi
export FONU_VERSION="$version"
echo "==> 构建并重建:fonu:$version(构建上下文是 Git 地址,会自动拉最新 $BRANCH;有缓存时只重编改动的部分)"
export HAVLINE_VERSION="$version"
echo "==> 构建并重建:havline:$version(构建上下文是 Git 地址,会自动拉最新 $BRANCH;有缓存时只重编改动的部分)"
docker compose -f "$COMPOSE_FILE" up -d --build
echo '==> 等待服务就绪…'
@@ -60,7 +60,7 @@ for _ in $(seq 1 30); do
echo "==> 升级完成"
echo " 当前版本:$(curl -fsS "$HEALTH_URL" 2>/dev/null)"
if [ -n "$previous_image" ]; then
echo " 回滚:FONU_VERSION=${previous_image#fonu:} docker compose -f $COMPOSE_FILE up -d"
echo " 回滚:HAVLINE_VERSION=${previous_image#havline:} docker compose -f $COMPOSE_FILE up -d"
fi
echo " 日志:docker logs --tail 50 $APP_CONTAINER"
exit 0
@@ -70,6 +70,6 @@ done
echo "服务在 60 秒内没有响应 $HEALTH_URL,请看:docker logs --tail 100 $APP_CONTAINER" >&2
if [ -n "$previous_image" ]; then
echo "如需回滚:FONU_VERSION=${previous_image#fonu:} docker compose -f $COMPOSE_FILE up -d" >&2
echo "如需回滚:HAVLINE_VERSION=${previous_image#havline:} docker compose -f $COMPOSE_FILE up -d" >&2
fi
exit 1
+11 -11
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Fonu 容器升级:在部署机(NAS)上直接构建,不需要再从别处传镜像包
# Havline 容器升级:在部署机(NAS)上直接构建,不需要再从别处传镜像包
#
# sudo bash scripts/upgrade.sh # 拉取最新源码 → 构建 → 重建容器
# sudo bash scripts/upgrade.sh --no-pull # 不拉代码,用当前源码重建
@@ -8,12 +8,12 @@
# 用 COMPOSE_FILE 指定编排文件(默认 docker-compose.yml):
# COMPOSE_FILE=docker-compose.host.yml sudo bash scripts/upgrade.sh
#
# 数据在 fonu-data 卷里,重建容器不会丢。每次构建按仓库 VERSION 打一个新 tag
# 数据在 havline-data 卷里,重建容器不会丢。每次构建按仓库 VERSION 打一个新 tag
# (旧的 tag 会留着),所以出问题可以直接 --rollback 回上一个版本。
set -euo pipefail
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}"
APP_CONTAINER="${APP_CONTAINER:-fonu}"
APP_CONTAINER="${APP_CONTAINER:-havline}"
HEALTH_URL="${HEALTH_URL:-http://127.0.0.1:6893/api/version}"
NO_PULL=0
ROLLBACK_TAG=""
@@ -33,7 +33,7 @@ while [ $# -gt 0 ]; do
done
if ! command -v docker >/dev/null 2>&1; then
echo "没有找到 docker 命令:这个脚本要在运行 Fonu 容器的那台机器上执行" >&2
echo "没有找到 docker 命令:这个脚本要在运行 Havline 容器的那台机器上执行" >&2
exit 1
fi
if [ ! -f "$COMPOSE_FILE" ]; then
@@ -49,12 +49,12 @@ current_image() {
echo "==> 升级前:$(current_image)"
if [ -n "$ROLLBACK_TAG" ]; then
if ! docker image inspect "fonu:$ROLLBACK_TAG" >/dev/null 2>&1; then
echo "本机没有 fonu:$ROLLBACK_TAG;已有 tag:$(docker images --format '{{.Tag}}' fonu | tr '\n' ' ')" >&2
if ! docker image inspect "havline:$ROLLBACK_TAG" >/dev/null 2>&1; then
echo "本机没有 havline:$ROLLBACK_TAG;已有 tag:$(docker images --format '{{.Tag}}' havline | tr '\n' ' ')" >&2
exit 1
fi
FONU_VERSION="$ROLLBACK_TAG" docker compose -f "$COMPOSE_FILE" up -d
echo "==> 已切回 fonu:$ROLLBACK_TAG"
HAVLINE_VERSION="$ROLLBACK_TAG" docker compose -f "$COMPOSE_FILE" up -d
echo "==> 已切回 havline:$ROLLBACK_TAG"
else
if [ "$NO_PULL" -eq 0 ]; then
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
@@ -68,9 +68,9 @@ else
echo '找不到 VERSION 文件:请在仓库根目录运行' >&2
exit 1
fi
FONU_VERSION="$(tr -d ' \t\n\r' < VERSION)"
export FONU_VERSION
echo "==> 构建并重建:fonu:$FONU_VERSION(首次构建较慢,之后有构建缓存)"
HAVLINE_VERSION="$(tr -d ' \t\n\r' < VERSION)"
export HAVLINE_VERSION
echo "==> 构建并重建:havline:$HAVLINE_VERSION(首次构建较慢,之后有构建缓存)"
docker compose -f "$COMPOSE_FILE" up -d --build
fi