From 50743981382aaa31914c1bc7ad11c1553b3b0ea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E6=96=87=E5=B3=B0?= Date: Thu, 28 May 2026 14:59:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=85=8D=E7=BD=AE=E7=B3=BB=E7=BB=9F=20?= =?UTF-8?q?+=20Linux=20=E6=9C=8D=E5=8A=A1=E5=AE=89=E8=A3=85=E8=84=9A?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 config/config.go:TOML 配置包,自动生成配置文件 + 缺失项补全 - Windows: conf/config.toml(基于工作目录) - Linux: /etc/portal_page/config.toml(FHS) - 配置项:data.dir, database.type(sqlite/mysql), server.addr, server.unix - Linux 默认 unix socket: /opt/portal_page/server.sock - database/db.go 重构支持 MySQL - handlers/upload.go 改用 config.GetUploadDir() - main.go 整合配置启动流程,支持 TCP/Unix socket 双模式 - 新增 install.sh:Linux 服务一键安装/卸载/管理脚本 - 拉取代码 → 编译 → 部署到 /opt/portal_page → systemd 服务 - 自动创建 portal_page 用户和 FHS 目录 - 支持 start/stop/restart/status --- config/config.go | 1 + install.sh | 355 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 356 insertions(+) create mode 100644 install.sh diff --git a/config/config.go b/config/config.go index a8f4854..2f0090f 100644 --- a/config/config.go +++ b/config/config.go @@ -79,6 +79,7 @@ func defaultConfig() *Config { // Linux 下遵循 FHS 标准 if runtime.GOOS == "linux" { cfg.Data.Dir = "/srv/portal_page" + cfg.Server.Unix = "/opt/portal_page/server.sock" } return cfg diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..3aa88c5 --- /dev/null +++ b/install.sh @@ -0,0 +1,355 @@ +#!/usr/bin/env bash +# +# portal_page 服务安装/管理脚本 +# 用法: +# sudo ./install.sh install — 安装/更新服务 +# sudo ./install.sh uninstall — 卸载服务 +# sudo ./install.sh start — 启动服务 +# sudo ./install.sh stop — 停止服务 +# sudo ./install.sh restart — 重启服务 +# sudo ./install.sh status — 查看服务状态 +# + +set -euo pipefail + +# ======================== 配置区 ======================== +SERVICE_NAME="portal_page" +SERVICE_USER="portal_page" +SERVICE_DESC="Portal Page - 门户网站服务" + +# 目录 +INSTALL_DIR="/opt/portal_page" # 程序安装目录 +DATA_DIR="/srv/portal_page" # 数据目录(数据库+上传文件) +CONFIG_DIR="/etc/portal_page" # 配置文件目录 +LOG_DIR="/var/log/portal_page" # 日志目录 +PID_DIR="/run/portal_page" # PID/Socket 目录 + +# Git 仓库 +GIT_REPO="https://git.lmve.net/kevin/portal_page.git" +GIT_BRANCH="main" + +# 编译临时目录 +BUILD_DIR="/tmp/portal_page_build" + +# systemd 服务文件路径 +SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" + +# ======================== 颜色输出 ======================== +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +info() { echo -e "${BLUE}[INFO]${NC} $*"; } +ok() { echo -e "${GREEN}[OK]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; } + +# ======================== 前置检查 ======================== +check_root() { + if [[ $EUID -ne 0 ]]; then + error "此脚本需要 root 权限运行,请使用 sudo" + fi +} + +# ======================== 用户管理 ======================== +ensure_user() { + if id "${SERVICE_USER}" &>/dev/null; then + ok "用户 ${SERVICE_USER} 已存在" + else + info "创建系统用户 ${SERVICE_USER} ..." + useradd -r -s /usr/sbin/nologin -d "${INSTALL_DIR}" -c "${SERVICE_DESC}" "${SERVICE_USER}" + ok "用户 ${SERVICE_USER} 创建成功" + fi +} + +# ======================== 目录管理 ======================== +ensure_dirs() { + info "检查并创建关键目录 ..." + + # 安装目录 + mkdir -p "${INSTALL_DIR}" + # 配置目录 + mkdir -p "${CONFIG_DIR}" + # 数据目录及子目录 + mkdir -p "${DATA_DIR}/uploads" + # 日志目录 + mkdir -p "${LOG_DIR}" + # PID/Socket 目录 + mkdir -p "${PID_DIR}" + + # 设置所有权 + chown -R "${SERVICE_USER}:${SERVICE_USER}" "${INSTALL_DIR}" + chown -R "${SERVICE_USER}:${SERVICE_USER}" "${DATA_DIR}" + chown -R "${SERVICE_USER}:${SERVICE_USER}" "${LOG_DIR}" + chown -R "${SERVICE_USER}:${SERVICE_USER}" "${PID_DIR}" + # 配置目录 root 拥有,服务用户可读 + chown -R "root:${SERVICE_USER}" "${CONFIG_DIR}" + chmod 750 "${CONFIG_DIR}" + + ok "目录结构就绪" +} + +# ======================== 编译 ======================== +build_binary() { + info "拉取最新代码 ..." + if [[ -d "${BUILD_DIR}/.git" ]]; then + cd "${BUILD_DIR}" + git fetch --all + git reset --hard "origin/${GIT_BRANCH}" + info "代码已更新到最新" + else + rm -rf "${BUILD_DIR}" + git clone -b "${GIT_BRANCH}" "${GIT_REPO}" "${BUILD_DIR}" + cd "${BUILD_DIR}" + info "代码克隆完成" + fi + + info "编译 Go 项目 ..." + CGO_ENABLED=0 go build -ldflags="-s -w" -o portal . + ok "编译完成: ${BUILD_DIR}/portal" +} + +# ======================== 部署文件 ======================== +deploy_files() { + info "部署文件到 ${INSTALL_DIR} ..." + + # 停止服务(如果正在运行),避免替换正在使用的二进制 + if systemctl is-active --quiet "${SERVICE_NAME}" 2>/dev/null; then + info "停止运行中的服务 ..." + systemctl stop "${SERVICE_NAME}" + fi + + # 复制二进制 + cp -f "${BUILD_DIR}/portal" "${INSTALL_DIR}/portal" + chmod 755 "${INSTALL_DIR}/portal" + + # 复制模板和静态资源 + cp -rf "${BUILD_DIR}/templates" "${INSTALL_DIR}/templates" + cp -rf "${BUILD_DIR}/static" "${INSTALL_DIR}/static" + + # 复制配置文件(仅当配置目录下不存在时) + if [[ ! -f "${CONFIG_DIR}/config.toml" ]]; then + info "配置文件不存在,程序首次启动将自动生成默认配置" + else + info "保留现有配置文件: ${CONFIG_DIR}/config.toml" + fi + + # 设置安装目录所有权 + chown -R "${SERVICE_USER}:${SERVICE_USER}" "${INSTALL_DIR}" + + ok "文件部署完成" +} + +# ======================== systemd 服务 ======================== +install_service() { + info "创建 systemd 服务文件 ..." + + cat > "${SERVICE_FILE}" </dev/null || error "需要 git,请先安装" + command -v go &>/dev/null || error "需要 go,请先安装" + + # 2. 创建用户 + ensure_user + + # 3. 创建目录 + ensure_dirs + + # 4. 拉取代码并编译 + build_binary + + # 5. 部署文件 + deploy_files + + # 6. 安装服务 + install_service + + # 7. 启动并设置开机自启 + info "启动服务 ..." + systemctl start "${SERVICE_NAME}" + systemctl enable "${SERVICE_NAME}" + + sleep 1 + if systemctl is-active --quiet "${SERVICE_NAME}"; then + ok "服务启动成功!" + else + error "服务启动失败,请检查日志: journalctl -u ${SERVICE_NAME} -n 50" + fi + + echo "" + info "========== 安装完成 ==========" + echo " 程序目录: ${INSTALL_DIR}" + echo " 配置文件: ${CONFIG_DIR}/config.toml" + echo " 数据目录: ${DATA_DIR}" + echo " 日志目录: ${LOG_DIR}" + echo " 服务管理: systemctl {start|stop|restart|status} ${SERVICE_NAME}" + echo " 查看日志: journalctl -u ${SERVICE_NAME} -f" + echo " 默认端口: 8080(可在配置文件中修改)" +} + +# ======================== 卸载 ======================== +do_uninstall() { + info "========== 卸载 ${SERVICE_NAME} ==========" + check_root + + # 停止服务 + if systemctl is-active --quiet "${SERVICE_NAME}" 2>/dev/null; then + systemctl stop "${SERVICE_NAME}" + info "服务已停止" + fi + + # 禁用开机自启 + if systemctl is-enabled --quiet "${SERVICE_NAME}" 2>/dev/null; then + systemctl disable "${SERVICE_NAME}" + info "已禁用开机自启" + fi + + # 删除服务文件 + if [[ -f "${SERVICE_FILE}" ]]; then + rm -f "${SERVICE_FILE}" + systemctl daemon-reload + info "服务文件已删除" + fi + + # 询问是否删除数据 + echo "" + warn "以下目录包含运行数据,是否删除?" + echo " [1] ${INSTALL_DIR} (程序文件)" + echo " [2] ${DATA_DIR} (数据库+上传文件)" + echo " [3] ${CONFIG_DIR} (配置文件)" + echo " [4] ${LOG_DIR} (日志)" + echo " [0] 全部保留(默认)" + echo " [a] 全部删除" + echo "" + read -rp "请选择 [0/a/组合如 14]: " choice + + case "${choice:-0}" in + 0) + info "保留所有数据目录" + ;; + a|A) + rm -rf "${INSTALL_DIR}" "${DATA_DIR}" "${CONFIG_DIR}" "${LOG_DIR}" + info "所有数据目录已删除" + ;; + *) + [[ "${choice}" == *1* ]] && rm -rf "${INSTALL_DIR}" && info "已删除 ${INSTALL_DIR}" + [[ "${choice}" == *2* ]] && rm -rf "${DATA_DIR}" && info "已删除 ${DATA_DIR}" + [[ "${choice}" == *3* ]] && rm -rf "${CONFIG_DIR}" && info "已删除 ${CONFIG_DIR}" + [[ "${choice}" == *4* ]] && rm -rf "${LOG_DIR}" && info "已删除 ${LOG_DIR}" + ;; + esac + + # 删除用户(如果没有其他依赖) + if id "${SERVICE_USER}" &>/dev/null; then + read -rp "是否删除系统用户 ${SERVICE_USER}?[y/N]: " del_user + if [[ "${del_user}" =~ ^[Yy]$ ]]; then + userdel "${SERVICE_USER}" + info "用户 ${SERVICE_USER} 已删除" + fi + fi + + # 清理编译目录 + rm -rf "${BUILD_DIR}" + + ok "卸载完成" +} + +# ======================== 服务控制 ======================== +do_start() { + check_root + systemctl start "${SERVICE_NAME}" + sleep 1 + if systemctl is-active --quiet "${SERVICE_NAME}"; then + ok "服务已启动" + else + error "服务启动失败,查看日志: journalctl -u ${SERVICE_NAME} -n 50" + fi +} + +do_stop() { + check_root + systemctl stop "${SERVICE_NAME}" + ok "服务已停止" +} + +do_restart() { + check_root + systemctl restart "${SERVICE_NAME}" + sleep 1 + if systemctl is-active --quiet "${SERVICE_NAME}"; then + ok "服务已重启" + else + error "服务重启失败,查看日志: journalctl -u ${SERVICE_NAME} -n 50" + fi +} + +do_status() { + systemctl status "${SERVICE_NAME}" --no-pager || true +} + +# ======================== 入口 ======================== +case "${1:-}" in + install) do_install ;; + uninstall) do_uninstall ;; + start) do_start ;; + stop) do_stop ;; + restart) do_restart ;; + status) do_status ;; + *) + echo "用法: sudo $0 {install|uninstall|start|stop|restart|status}" + echo "" + echo " install — 完整安装/更新(拉代码+编译+部署+启动+开机自启)" + echo " uninstall — 卸载服务(可选保留数据)" + echo " start — 启动服务" + echo " stop — 停止服务" + echo " restart — 重启服务" + echo " status — 查看服务状态" + exit 1 + ;; +esac