# funops **Repository Path**: uprain/funops ## Basic Information - **Project Name**: funops - **Description**: No description available - **Primary Language**: Unknown - **License**: AGPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-22 - **Last Updated**: 2026-09-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README
# FunOps FunOps **一个轻量、务实的 CI/CD 与主机运维平台,为需要简单构建部署自动化的团队而生。**

Python 3.9 Vue 3 Django MySQL 8

## 简介 FunOps 是一个聚焦日常需求的 CI/CD 平台:项目管理、环境配置、可复用凭证、参数化构建计划、构建记录、实时日志、通知机器人与 GitLab Webhook 触发。目标是在保留 Jenkins 自由风格任务那种直观工作流的同时,让团队对构建状态、执行日志和部署结果有清晰的可见性。 在此基础上,FunOps 扩展了**主机管理**能力:Linux 主机台账、Web 终端(xterm.js + WebSocket)、敏感命令管控(高危命令拒绝/告警 + 全量审计),覆盖「构建 → 部署 → 上机操作」的完整链路。 构建计划支持 **Jenkins 风格流水线**:用 `pipeline.yml` 描述 `agent / stages / when / env / triggers / notify`,编辑器内置语法校验与错误定位,可实时预览流水线流程图;执行位置可选本机运行或指定远程主机运行。项目根目录附带 `demo-gin-app/` 示例项目,可直接用于验证两条执行路径。 ## 功能特性 **CI/CD** - 项目与服务管理 - 环境管理(开发 / 测试 / 预发 / 生产) - 构建计划配置:分支、参数、阶段、外部脚本仓库、通知 - Jenkins 风格流水线:`pipeline.yml` 编辑(语法校验、错误定位)、流程图实时预览 - 执行位置可选:本机运行,或构建目录上传到指定远程主机执行 - 手动构建与 GitLab Webhook 自动触发 - 构建日志实时流式输出、阶段级日志视图 - 构建记录、仪表盘统计、构建趋势 - 凭证管理:GitLab Token、SSH 密钥、通用 Git 用户名密码、kubeconfig - 多代码仓库支持:GitLab / GitHub / Gitee / 通用 Git(Codeup 等) - 通知机器人配置与测试发送(钉钉 / 企业微信 / 飞书 / WebHook) **主机管理** - Linux 主机台账:环境分组、负责人、K/V 标签、配置信息采集、批量导入 - Web 终端:浏览器内直接 SSH 到目标主机(xterm.js + WebSocket) - 敏感命令管控:内置高危命令规则(格式化磁盘、递归强删、关机重启等), 按主机 / 标签 / 角色范围生效,命中后拒绝或告警 - 全量命令审计与终端连接审计 **证书管理** - SSL 证书监控:到期时间跟踪、周期巡检、异常日志、批量导入、模板下载 **系统管理** - 用户、角色、权限、LDAP、登录日志、操作日志、安全配置(水印 / 会话超时 / 登录保护) ## 技术栈 | 端 | 技术 | |---|---| | 前端 | Vue 3、Ant Design Vue 4.x、Vue Router、Axios、ECharts、Vite、xterm.js | | 后端 | Python 3.9+、Django 4.2、Uvicorn / ASGI(WebSocket 原生实现)、paramiko、GitPython、python-gitlab、PyJWT、ldap3 | | 存储 | MySQL 8(唯一必需的外部中间件,不依赖 Redis / 消息队列 / 对象存储) | ## 目录结构 ```text . |-- funops-server/ # Django 后端与 API(内层 Django 工程包:funops/) | |-- Dockerfile # 后端镜像(python:3.9-slim + uvicorn) | |-- conf/config.yml # 后端非敏感默认配置(敏感项全部走环境变量) | `-- requirements.txt # Python 依赖 |-- funops-web/ # Vue 3 前端应用 | |-- Dockerfile # 前端镜像(Node 构建 → Nginx 托管) | |-- nginx.conf # 前端容器 Nginx 配置(SPA + /api + /ws 代理) | `-- vite.config.js # 开发服务器 / 代理配置 |-- demo-gin-app/ # Go Gin 示例项目(流水线测试用) |-- docs/ | |-- sql/ # funops_init.sql(幂等初始化)+ 增量迁移脚本 | `-- DESIGN.md # 设计文档(各模块设计原理与实现方式) |-- docker-compose.yml # 方式二:MySQL + 后端 + 前端全容器一键拉起 |-- start.sh # 方式一:本地一键启动 / 停止(MySQL + 后端 + 前端) |-- .env.example # 环境变量示例(复制为 .env 使用,.env 不入仓库) `-- README.md ``` ## 快速开始 两种启动方式,**任选其一**(不要同时启动,两者都占用后端 `8900` 端口): | 方式 | 组件位置 | 适用场景 | 入口 | |---|---|---|---| | 方式一:本地部署 | MySQL 容器/本机实例 + 后端/前端跑在宿主机 | 源码开发、断点调试、前后端热更新 | `./start.sh` | | 方式二:全容器部署 | MySQL、后端、前端全部在容器 | 快速体验、演示、部署验证 | `docker compose up -d` | 默认登录账号:**admin / admin123**(首次登录请立即修改密码) ### 方式一:本地一键启动(推荐用于开发) #### 前置要求 - Python 3.9+ - Node.js 18+ - Docker / OrbStack(**可选**:仅当本机没有 MySQL 时才需要,脚本用它自动拉起 MySQL 容器) - Git #### 启动 ```bash git clone https://gitee.com/uprain/funops.git cd funops chmod +x start.sh ./start.sh ``` 脚本会自动完成: 1. **MySQL**:优先复用本机 `3306` 上已有的 MySQL;端口空闲时用 `docker compose` 拉起 `funops-mysql` 容器(数据存在命名卷 `funops-mysql-data` 里),并等待就绪;检测到没有业务表时自动导入 `docs/sql/funops_init.sql` 2. **后端**:不存在 `funops-server/.venv` 时自动创建,依赖缺失或 `requirements.txt` 变更时自动安装,然后以 uvicorn 启动 3. **前端**:`funops-web/node_modules` 缺失时自动 `npm install`,然后启动 Vite dev server 4. 逐项做真实健康检查(后端 HTTP 探活、前端 HTTP 探活),全部就绪后才提示启动成功 停止 / 查看状态: ```bash ./start.sh --stop # 停止后端与前端(MySQL 容器若由脚本拉起则一并停止,数据保留) ./start.sh --status # 查看三个组件的运行状态 ./start.sh --restart # 先停再起 ./start.sh --help # 查看全部参数 ``` #### 访问地址 ```text 前端界面 http://localhost:8001 后端 API http://localhost:8900/api/ MySQL 127.0.0.1:3306 (库名 funops) ``` #### 可覆盖的环境变量 ```bash FUNOPS_ENV=dev # 运行环境 dev / test / prod BACKEND_PORT=8900 # 后端端口 WEB_PORT=8001 # 前端端口 MYSQL_PORT=3306 # MySQL 端口 FUNOPS_MYSQL_PASSWORD=funopspasswd123 # MySQL root 密码(测试默认值) FUNOPS_SECRET_KEY=... # JWT 密钥(test/prod 必填;dev 缺省用内置开发密钥) PIP_INDEX=https://mirrors.aliyun.com/pypi/simple/ # pip 镜像 NPM_REGISTRY=https://registry.npmmirror.com # npm 镜像 ``` 例:`WEB_PORT=9000 BACKEND_PORT=9100 ./start.sh` #### 手动分步启动(不用脚本) ```bash # 1) MySQL(已有 MySQL 可跳过,只需导入一次表结构) docker compose up -d mysql docker exec -i funops-mysql mysql --default-character-set=utf8mb4 -uroot -pfunopspasswd123 funops < docs/sql/funops_init.sql # 2) 后端 cd funops-server python3 -m venv .venv .venv/bin/python -m pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ .venv/bin/python -m uvicorn funops.asgi:application --host 0.0.0.0 --port 8900 # 3) 前端(另开一个终端) cd funops-web npm install --registry=https://registry.npmmirror.com npm run dev ``` ### 方式二:Docker Compose 全容器启动 #### 前置要求 - Docker / OrbStack(内存建议 ≥ 4G) #### 启动 ```bash git clone https://gitee.com/uprain/funops.git cd funops docker compose up -d # 首次会构建前后端镜像,约需几分钟 docker compose ps # 三个容器都应为 running / healthy ``` 首次启动时 MySQL 数据卷为空,会自动执行 `docs/sql/funops_init.sql` 完成建库建表与默认数据写入。 #### 访问地址 ```text 前端界面 http://localhost:8080 (WEB_PORT 可覆盖) 后端 API http://localhost:8900 (BACKEND_PORT 可覆盖) MySQL 127.0.0.1:3306 (MYSQL_PORT 可覆盖) ``` 前端容器内的 Nginx 同时托管静态文件并把 `/api/`、`/ws/` 反向代理到后端容器,因此浏览器只访问 `8080` 一个端口,**没有跨域配置**。 #### 常用命令 ```bash docker compose logs -f funops-server # 后端日志(含构建任务执行日志) docker compose logs -f funops-web # Nginx 访问日志 docker compose exec funops-server tail -f /app/logs/django.log docker compose restart funops-server # 重启后端 docker compose down # 停止并删除容器(数据保留) docker compose up -d --build # 代码更新后重新构建并启动 ``` > ⚠️ 不要用 `docker compose down -v`:`-v` 会删除 `funops-mysql-data` 卷,数据库需重新导入。 #### 容器部署的功能边界 - 后端容器内**没有** `dockerd` / `kubectl` / `maven`。构建计划若勾选「本机执行」且阶段里用到这些命令,会失败; 此时请把执行位置设为「远程主机」,或改用方式一(本地部署,直接用宿主机工具链)。 - 构建产物保存在命名卷 `funops-build-data`(容器内 `/data`),容器重建不丢。 ## 国内部署与镜像源 仓库里的部署脚本与 Dockerfile **默认已全部切换到国内源**,国内服务器可直接构建,无需改任何文件: | 环节 | 默认源 | 位置 | | --- | --- | --- | | Python 依赖 | 阿里云 PyPI `mirrors.aliyun.com/pypi/simple` | `funops-server/Dockerfile`、`start.sh` | | Node 依赖 | npmmirror `registry.npmmirror.com`(`package-lock.json` 的 resolved 同为该源) | `funops-web/Dockerfile`、`start.sh` | | Debian apt | 阿里云 `mirrors.aliyun.com`(自动替换 `deb.debian.org` **和** `security.debian.org`) | `funops-server/Dockerfile` | | Alpine apk / Go 模块 | 阿里云 Alpine、`goproxy.cn` | `demo-gin-app/Dockerfile` | ### 想换源 ```bash # 单独构建时 docker build --build-arg APT_MIRROR=mirrors.tuna.tsinghua.edu.cn \ --build-arg PIP_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple \ ./funops-server # 或用 .env / 环境变量覆盖(compose 会自动读取) # PIP_INDEX=... NPM_REGISTRY=... APT_MIRROR=... ``` ### 构建中断:`Temporary failure resolving 'mirrors.aliyun.com'` 这是**容器里的 DNS 解析失败**,不是镜像源的问题——日志里通常会同时看到另一个域名也解析不了 (比如 `security.debian.org`)。先确认宿主自己是好的: ```bash # 1) 宿主能否解析(正常应返回 IP) getent hosts mirrors.aliyun.com # 2) 容器里能否解析(失败=容器网络 DNS 有问题,此时换任何源都没用) docker run --rm alpine getent hosts mirrors.aliyun.com # 或 alpine 也没拉取成功时:docker run --rm busybox nslookup mirrors.aliyun.com ``` 修复二选一: **① daemon 级指定 DNS(推荐,对所有容器与构建同时生效)** ```bash sudo tee /etc/docker/daemon.json <<'EOF' { "dns": ["223.5.5.5", "119.29.29.29", "114.114.114.114"] } EOF sudo systemctl daemon-reload && sudo systemctl restart docker ``` **② 让构建直接用宿主网络**(源文件不用改,只是换个启动方式): ```bash docker build --network host -t funops/server:latest ./funops-server ``` 或在 `docker-compose.yml` 每个 `build:` 下加一行 `network: host`(**需要 Compose v2 的 `docker compose`**, 已停止维护的 `docker-compose` v1 不支持该字段): ```yaml funops-server: build: context: ./funops-server network: host ``` > 根因通常是 daemon 默认的 DNS 转发链路断了(宿主 `/etc/resolv.conf` 指向某个容器访问不到的 > 地址,或 firewalld/iptables 规则把桥接网络的 DNS 挡掉了)。这类问题只改 Dockerfile 治不了。 ### 拉不动 Docker Hub 基础镜像 国内服务器常见现象:`python:3.9-slim`、`node:22-alpine`、`mysql:8` 拉取超时。 两种解法,选一个即可: **① 给 Docker daemon 配镜像加速(推荐,对所有镜像生效)** ```bash sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<'EOF' { "registry-mirrors": ["https://<你的加速地址>"] } EOF sudo systemctl restart docker ``` 加速地址优先用**云厂商给的个人专属地址**(阿里云:容器镜像服务控制台 → 镜像加速器, 形如 `https://xxxxxx.mirror.aliyuncs.com`),也可以配公共加速服务。**这类地址生命周期普遍很短, 下面这份清单会过期,请务必先验证再用**: ```bash for m in docker.1ms.run docker.m.daocloud.io docker.xuanyuan.me docker.1panel.live \ docker.nju.edu.cn mirror.baidubce.com hub-mirror.c.163.com mirror.ccs.tencentyun.com; do printf "%-30s %s\n" "$m" "$(curl -s -o /dev/null -m 8 -w '%{http_code}' https://$m/v2/)" done ``` 返回 `401` 即健康(registry v2 的鉴权挑战),`000` / `403` / 超时说明在你这条网络上不可用。 挑 2~3 个可用的填进 `registry-mirrors`,Docker 会按顺序重试: ```bash sudo tee /etc/docker/daemon.json <<'EOF' { "dns": ["223.5.5.5", "119.29.29.29"], "registry-mirrors": [ "https://docker.1ms.run", "https://docker.m.daocloud.io", "https://docker.xuanyuan.me" ] } EOF sudo systemctl daemon-reload && sudo systemctl restart docker docker info | grep -A4 "Registry Mirrors" # 确认生效 docker pull hello-world # 实测一次 ``` 配好后本仓库无需任何改动,直接 `docker compose up -d --build`。 **② 只覆盖本项目用到的镜像(不想动 daemon 配置时)** 在 `.env` 里写完整镜像地址即可: ```bash PYTHON_BASE_IMAGE=<加速前缀>/python:3.9-slim NODE_BASE_IMAGE=<加速前缀>/node:22-alpine NGINX_BASE_IMAGE=<加速前缀>/nginx:1.27-alpine MYSQL_IMAGE=<加速前缀>/mysql:8 ``` > 之所以不把这些前缀写死在 Dockerfile 里:第三方公共加速地址生命周期普遍很短, > 写死就是给半年后的自己埋雷。默认值保持官方镜像名,需要时用一个环境变量切换,改回来也只是一行。 ## 配置说明 **敏感项全部走环境变量,仓库内不存放任何密钥/密码。** ### 多环境(FUNOPS_ENV) | 环境 | debug / CORS 放开 | SECRET_KEY | |---|---|---| | `dev`(默认) | 开 | 未设置时用内置开发密钥(禁止用于生产) | | `test` | 开 | **必须显式设置** `FUNOPS_SECRET_KEY`,否则后端拒绝启动 | | `prod` | 关 | **必须显式设置** `FUNOPS_SECRET_KEY`,否则后端拒绝启动 | ### 环境变量清单 复制仓库根目录 `.env.example` 为 `.env` 后按需修改(`.env` 已被 git 忽略;`./start.sh` 与 `docker compose` 都会自动读取,已 export 的变量优先): ```text FUNOPS_ENV # dev / test / prod(默认 dev) FUNOPS_SECRET_KEY # JWT 签名密钥(test/prod 必填) FUNOPS_DEBUG # 调试开关(默认按环境决定) FUNOPS_ALLOWED_HOSTS # 允许的 Host,逗号分隔(默认 '*') FUNOPS_CORS_ALLOW_ALL # CORS 放开全部来源(默认按环境决定) FUNOPS_MYSQL_HOST # 默认 127.0.0.1(容器内为 mysql) FUNOPS_MYSQL_PORT # 默认 3306 FUNOPS_MYSQL_DB # 默认 funops FUNOPS_MYSQL_USER # 默认 root FUNOPS_MYSQL_PASSWORD # 默认 funopspasswd123(仅测试环境;生产务必修改) FUNOPS_BUILD_ROOT # 构建产物根目录,默认 funops-server/../data ``` `funops-server/conf/config.yml` 只保留**非敏感默认值**(host/port/库名/字符集等),环境变量优先级高于它。新增中间件(如 Redis)时照此模式:config.yml 加一节 + `FUNOPS__*` 环境变量。 > 注意:MySQL root 密码在**数据卷首次初始化时**确定。已有 `funops-mysql-data` 卷的环境修改 `FUNOPS_MYSQL_PASSWORD` 不会改变卷内 root 密码,需要进容器改密或删卷重建。 ## 数据库初始化 表结构以 SQL 为准,**项目不使用 Django migration**: - `docs/sql/funops_init.sql`:幂等初始化脚本。全新环境自动建库建表并写入默认数据(管理员账号、角色权限、内置高危命令规则、菜单权限等);已有环境重复导入不会丢数据,缺失的表 / 列 / 默认数据会自动补齐。 - `docs/sql/2026-*.sql`:增量迁移脚本,按文件名日期顺序执行。导入时建议显式指定字符集,否则中文会变 `????`: ```bash docker exec -i funops-mysql mysql --default-character-set=utf8mb4 -uroot -pfunopspasswd123 funops < docs/sql/xxx.sql ``` 修改 `funops-server/apps/models.py` 后,需要同步修改 SQL(或在库里手工建表),否则运行期会报表不存在。 ## 二次开发说明 - **前端接口地址**:开发环境下 `vite.config.js` 已把 `/api`、`/ws` 代理到后端(默认 `127.0.0.1:8900`),前端代码统一使用相对路径调用接口即可,无需配置 `VITE_API_URL`、无跨域问题。后端不在本机时用环境变量调整: ```bash BACKEND_HOST=192.168.1.10 BACKEND_PORT=8900 npm run dev ``` - **构建任务执行位置**: - 「本机」指 FunOps 后端所在机器(本地部署即宿主机,容器部署即 funops-server 容器) - 「远程主机」通过 `agent_hosts` 指定主机名或 `agent_label` 指定标签,多台按顺序逐台执行,走 SSH 上传构建目录后流式执行 - **流水线定义**:数据库中的结构化字段是唯一事实源,`pipeline.yml` 是它的编辑视图,两者由 `funops-server/apps/utils/pipeline_yml.py` 双向转换。 - **前端开发规范**:主题令牌集中在 `funops-web/src/assets/css/theme.css`(`--fp-*`),页面样式优先复用令牌,避免硬编码颜色。 ## 常见问题排查 **端口被占用** ```bash lsof -nP -iTCP:8900 -sTCP:LISTEN # 后端 lsof -nP -iTCP:8001 -sTCP:LISTEN # 前端 # 或直接换端口 WEB_PORT=9000 BACKEND_PORT=9100 ./start.sh ``` **前端页面能打开但接口全 404 / 502** 说明 `/api` 没代理到后端。本地部署检查后端是否在 `BACKEND_PORT` 上监听;容器部署检查 `docker compose ps` 里 funops-server 是否 healthy。 **后端启动报数据库连接失败** 先确认 MySQL 已就绪,再核对 `.env`(参考 `.env.example`)或 `FUNOPS_MYSQL_*` 环境变量。容器内连的是服务名 `mysql`,宿主机连的是 `127.0.0.1`。 **导入 SQL 后中文变成问号** 没指定字符集,重导一次并加上 `--default-character-set=utf8mb4`。 **Web 终端连不上** Web 终端依赖 WebSocket,需要 `/ws/` 的代理支持 Upgrade。本地部署由 Vite 代理处理;容器部署由 `funops-web/nginx.conf` 的 `/ws/` 段处理。若前面还挂了外层 Nginx,记得同样透传 `Upgrade` / `Connection` 头。 **日志位置** ```text 本地部署 logs/backend.log、logs/web.log(脚本输出);funops-server/logs/django.log 容器部署 docker compose logs -f funops-server;容器内 /app/logs/ ``` ## 项目状态 FunOps 仍在积极开发中,核心工作流已经可用,功能与细节持续完善。欢迎在使用中反馈问题与建议。 ## License 本项目基于 [LICENSE](LICENSE) 协议开源。