Compare commits

...
130 Commits
Author SHA1 Message Date
wanghe 3cd0c89c4c Update CONTRIBUTING.md 2024-03-20 22:36:07 +08:00
ssongliu c27fcf18fc feat: 升级依赖 fit2cloud-ui-plus (#4250) 2024-03-20 10:50:07 +00:00
zhengkunwang b1f20d125e fix: 解决 i18n 导致页面错误的问题 (#4247) 2024-03-20 08:22:07 +00:00
zhengkunwang cd43bff44d fix: 解决文件选择框关闭再打开依然选中的问题 (#4243) 2024-03-20 03:32:07 +00:00
ssongliu 8cf8375911 feat: 镜像拉取日志 Unicode 转义 (#4239) 2024-03-19 14:40:06 +00:00
zhengkunwang ebc3195df4 feat: ACL 规则增加匹配方式 (#4237) 2024-03-19 12:58:06 +00:00
zhengkunwang ce69f6a142 fix: 解决应用安装快速跳转失败的问题 (#4236) 2024-03-19 09:12:07 +00:00
zhengkunwang f6777d51b5 fix: 解决应用安装快速跳转失败的问题 (#4235) 2024-03-19 07:36:06 +00:00
zhengkunwang d916f2ba6f fix: 解决网站配置文件更新之后参数丢失的问题 (#4231)
Refs https://github.com/1Panel-dev/1Panel/issues/4171
2024-03-19 05:50:06 +00:00
zhengkunwang 0e159f944a feat: 修改应用已安装和可升级页面的标签选择 (#4229) 2024-03-18 14:34:10 +00:00
Anye 329bb94d3c 增加PHP模板 (#4227)
* PHP模板增加“苹果CMS-V10”

* PHP增加“SeaCMS”扩展模板
2024-03-18 22:32:06 +08:00
zhengkunwang 7f2f7fa195 feat(waf): 优化配置更新接口 (#4226) 2024-03-18 13:16:10 +00:00
ssongliu 67c79bcde4 fix: 还原 Makefile 内容 (#4225) 2024-03-18 09:50:08 +00:00
zhengkunwang d7d7c7fd73 feat(waf): 增加内部接口请求安全性 (#4224) 2024-03-18 09:36:08 +00:00
ssongliu 94028d97cf style: 修改重启面板及服务器按钮位置 (#4221) 2024-03-18 09:34:08 +00:00
zhengkunwang cbfedb5d5e feat(waf): 优化请求次数记录方式 (#4210) 2024-03-16 14:44:06 +00:00
ssongliu 7784ee9575 fix: 解决快照恢复时校验 daemon.json 路径失败的问题 (#4205)
Refs #4206
2024-03-16 02:12:06 +00:00
ssongliu 29d5d82fa5 feat: 增加 xpack Makefile (#4204) 2024-03-15 09:26:08 +00:00
zhengkunwang 46f01032b8 fix: 解决回收站文件放置错误问题 (#4203)
Refs https://github.com/1Panel-dev/1Panel/issues/4199
2024-03-15 08:40:07 +00:00
zhengkunwang 341f45b4ef feat: 修改网站导入证书的描述 (#4202)
Refs https://github.com/1Panel-dev/1Panel/issues/4184
2024-03-15 08:08:07 +00:00
ssongliu a0907592b2 fix: 解决未登录情况下的页面跳转问题 (#4195) 2024-03-15 08:00:08 +00:00
zhengkunwang 825ebd19fb feat: 守护进程增加加载状态 (#4201)
Refs https://github.com/1Panel-dev/1Panel/issues/4129
2024-03-15 07:40:08 +00:00
zhengkunwang 8f24432a01 feat: 网站搜索增加网站关联域名匹配 (#4198)
Refs https://github.com/1Panel-dev/1Panel/pull/4198
2024-03-15 07:38:08 +00:00
zhengkunwang 6ce35b723b fix: 解决 Node 运行环境状态异常问题 (#4197) 2024-03-15 07:36:07 +00:00
ssongliu a33eabb4f0 fix: 移除计划任务报告中的 ANSI 转义 (#4193)
Refs #4097
2024-03-15 03:56:07 +00:00
ssongliu 34a6587ebc fix: 升级依赖 jackc/pgx/v5 (#4190) 2024-03-15 02:30:07 +00:00
ssongliu 17020ffe9f fix: 解决计划任务列表备份账号显示问题 (#4187)
Refs #4096
2024-03-14 10:24:07 +00:00
ssongliu 0f9b0d5d82 feat: 同步数据库时标记已删除数据库 (#4185)
Refs #4165
2024-03-14 10:22:07 +00:00
zhengkunwang be36103475 feat: 增加应用商店显示的过滤数量 (#4183) 2024-03-14 08:02:06 +00:00
ssongliu a3b5869c2b fix: 移除未使用依赖 2024-03-14 15:19:39 +08:00
maninhill 0807da41a5 Update README.md 2024-03-14 15:19:04 +08:00
maninhill ea6870e3c9 Update README.md 2024-03-14 15:07:13 +08:00
ssongliu 70b7daf23a fix: 升级依赖 google.golang.org/protobuf (#4176) 2024-03-14 03:24:06 +00:00
建国 1d01fa1e14 fix: 解决计划任务报告刷新异常的问题 (#4177)
Refs #4175
2024-03-14 03:14:06 +00:00
Akirami b643fa2ca3 fix: 修复应用商店详情页面部分图标比例错误 (#4168)
* fix: 修复应用商店详情页面部分图标比例错误

* fix: 替换为内联样式
2024-03-13 22:28:03 +08:00
zhengkunwang 77f4f64f0d feat(waf): 优化规则 (#4166) 2024-03-12 12:34:07 +00:00
ssongliu 3b697c7520 feat: 快照支持设置排除目录 (#4154)
Refs #4076
2024-03-12 03:04:08 +00:00
刘瑞斌 8ca6b9b6fa chore: add pnpm-lock.yaml to .gitignore (#4162) 2024-03-12 10:40:37 +08:00
zhengkunwang 0b66be7a3a fix: 解决安装应用过程中出现的状态异常问题 (#4156) 2024-03-11 12:34:08 +00:00
ssongliu f9f68a975c feat: 增加界面设置功能 (#4128) 2024-03-11 09:12:08 +00:00
L1nyz-tel ba63907a65 fix: 解决命令注入waf被绕过的问题 (#4131) 2024-03-08 22:43:56 +08:00
zhengkunwang 2687c91e65 feat: 升级 github.com/go-jose/go-jose/v3 版本到 v3.0.3 (#4126) 2024-03-08 08:50:06 +00:00
zhengkunwang eb82c168c5 feat: 升级 github.com/go-jose/go-jose/v3 版本到 v3.0.3 (#4121) 2024-03-08 08:14:07 +00:00
ssongliu 21b1242a94 fix: 解决部分 OneDrive 上传大文件失败的问题 (#4117)
Refs #4125
2024-03-08 08:12:06 +00:00
zhengkunwang a6784772b9 feat: 增加一个外部访问地址校验规则 (#4109)
Refs https://github.com/1Panel-dev/1Panel/issues/4091
2024-03-07 10:19:06 +00:00
igophper cf4fcaa07f fix: 解决编辑ssh端口同步到终端本地服务器问题 (#4043) (#4093) 2024-03-07 11:48:00 +08:00
zhengkunwang 62b1bb3191 feat: 升级 element-plus 到 2.6.0 (#4092) 2024-03-07 03:07:06 +00:00
ssongliu 8ce066a9d4 fix: 修改升级过程中的架构判断 (#4102) 2024-03-07 03:05:05 +00:00
wanghe-fit2cloud 7d8da406cf feat: 增加论坛链接 2024-03-07 10:52:00 +08:00
mobeicanyue b3d6b2b0de Issue 跳转 GitHub discussions (#4090) 2024-03-07 10:47:53 +08:00
zhengkunwang 21f24f2e10 feat(waf): 增加全局设置 (#4088) 2024-03-06 10:16:08 +00:00
ssongliu fe717d993b fix: 快照重试后更新列表状态 (#4086) 2024-03-06 07:58:07 +00:00
zhengkunwang 703da8a09a fix: 解决网站日志查看报错的问题 (#4082)
Refs https://github.com/1Panel-dev/1Panel/issues/3789
2024-03-06 03:56:07 +00:00
ssongliu a99f19471b fix: 优化计划任务脚本执行输出 (#4080) 2024-03-06 02:36:07 +00:00
zhengkunwang ca3be03ae7 feat: 本地应用同步增加校验 (#4071) 2024-03-05 10:10:08 +00:00
ssongliu 71ca8016e1 fix: 解决命令执行超时未生效的问题 (#4070) 2024-03-05 10:06:08 +00:00
ssongliu 5633bdd41e fix: 快照前清空服务器地址 (#4068) 2024-03-05 04:04:07 +00:00
ssongliu ab11d7257c fix: 快照重试时忽略已成功步骤 (#4067) 2024-03-05 03:14:07 +00:00
ssongliu 39b8de7ada fix: 限制快照跨架构恢复 (#4064)
Refs #4056
2024-03-04 14:59:11 +00:00
zhengkunwang c49c4b7863 feat(waf): 增加概览页 (#4060) 2024-03-04 08:53:06 +00:00
ssongliu d42df7184c style: 修改主机记住密码显示标题 (#4061) 2024-03-04 08:37:05 +00:00
ssongliu e7f172520a pref: 优化 MySQL 备份恢复方式 (#4059)
Refs #3955
2024-03-04 08:35:05 +00:00
zhengkunwang dca29174ea feat: 同步本地应用增加 data.yml 正确性验证 (#4058)
Refs https://github.com/1Panel-dev/1Panel/issues/4052
2024-03-04 03:53:03 +00:00
ssongliu 94181538a9 fix: 解决单位转换导致的内存限制问题 (#4055)
Refs #4040
2024-03-04 03:29:04 +00:00
zhengkunwang e9cc7bf1cf feat(waf):增加请求日志统计 (#4048) 2024-03-02 14:45:02 +00:00
ssongliu fa1e945b8d pref: 处理代码警告 (#4036) 2024-03-01 08:55:02 +00:00
zhengkunwang 94702a79a4 feat(waf): 修改攻击日志格式 (#4024) 2024-03-01 05:53:02 +00:00
ssongliu 24f573f9fd fix: 修改快照恢复逻辑 (#4031) 2024-03-01 13:51:34 +08:00
ssongliu 7e4606eda5 feat: 增加防篡改功能 (#4016) 2024-03-01 05:51:01 +00:00
ssongliu 73e118a3e5 style: 封装表格搜索组件 (#4028)
Refs #4017
2024-03-01 03:21:01 +00:00
ssongliu 514d68d395 fix: 解决容器资源使用率获取错误的问题 (#4023)
Refs #4022
2024-02-29 03:08:18 +00:00
QuentinHsu d0bb07796b fix: 初始化版本号变量为字符串类型,默认为空字符串 (#4003) 2024-02-28 17:10:47 +08:00
ssongliu 66a451fc1f fix: 解决登陆页刷新跳转的问题 (#4014)
Refs #3739
2024-02-28 09:06:15 +00:00
zhengkunwang c13e2a36cc feat: 增加 x-Pack 菜单 (#4009) 2024-02-28 08:44:15 +00:00
zhengkunwang 2affd2bc79 feat: 增加 waf 模块 (#4008) 2024-02-28 06:34:09 +00:00
QuentinHsu c510a028db fix: 解决重复访问容器内部时,终端显示宽度异常的问题 (#3996) 2024-02-27 14:54:57 +08:00
ssongliu 121a1ff5da fix: 修改快照 json 文件生成逻辑 (#3993) 2024-02-27 03:58:12 +00:00
ssongliu 1ecc53dad3 feat: 面板帮助文档跳转到具体目录 (#3992) 2024-02-27 03:30:13 +00:00
zhengkunwang 279b3942b4 fix: 解决应用同步异常的问题 (#3991) 2024-02-27 03:28:12 +00:00
ssongliu a0af04bbf7 fix: 调整快照回滚逻辑 (#3989) 2024-02-27 02:20:12 +00:00
ssongliu d5d1125660 fix: 解决快照恢复过程中生成回滚文件不完整的问题 (#3987) 2024-02-26 22:45:52 +08:00
wanghe-fit2cloud 08801ebfcd chore: Update README.md 2024-02-26 18:05:40 +08:00
zhengkunwang 262cbb9405 fix: 解决删除 openresty 之后没有同步删除 php 运行容器的问题 (#3984) 2024-02-26 10:04:12 +00:00
zhengkunwang 96233ee9b2 fix: 解决拖动上传文件夹错误的问题 (#3983) 2024-02-26 10:02:11 +00:00
ssongliu 49dcb094dc fix: 解决容器镜像清理的显示问题 (#3980) 2024-02-26 06:14:10 +00:00
zhengkunwang f0d5aca657 fix: 解决应用重建失败的问题 (#3967) 2024-02-23 10:38:40 +00:00
zhengkunwang 58f58f0fac fix: 解决清空文件后保存失败的问题 (#3965) 2024-02-23 10:36:40 +00:00
QuentinHsu 3bd6f46fe2 perf: 应用商店的应用介绍样式 (#3961) 2024-02-22 23:03:56 +08:00
zhengkunwang e427ccf70c feat: 修改应用重建逻辑 (#3953) 2024-02-22 09:12:08 +00:00
ssongliu 1b47e8dd63 fix: 解决 Pr 导致的代码冲突 (#3949) 2024-02-22 07:34:07 +00:00
ssongliu b668f55222 fix: 移除项目未使用代码 (#3948) 2024-02-22 07:32:07 +00:00
wanghe-fit2cloud 9a127d9afe chore: 升级 Node.js 和 Golang 版本 2024-02-22 15:23:20 +08:00
ssongliu 63484601b5 fix: 忽略 WebDAV 获取端口信息错误日志 (#3946) 2024-02-22 05:42:06 +00:00
ssongliu ef5e42cf0b fix: 解决远程数据库添加验证失败的问题 (#3942) 2024-02-21 11:08:29 +00:00
ssongliu 50a83e7e11 fix: Cookie 启用 httponly (#3941) 2024-02-21 11:06:28 +00:00
ssongliu 2475c82a63 fix: 修改 Mysql 远程数据库添加校验 (#3939)
Refs #3936
2024-02-21 09:00:30 +00:00
ssongliu 61ff55e157 pref: 计划任务数据库备份增加实例标签 (#3938)
Refs #3937
2024-02-21 08:58:28 +00:00
ssongliu dbc7799e89 fix: 解决代码冲突 (#3930) 2024-02-20 12:30:08 +00:00
wangdan-fit2cloud 6275e06806 feat: 样式优化 (#3928) 2024-02-20 18:20:45 +08:00
ssongliu e4f9ec32cd fix: 快照时排除备份目录下的快照文件 (#3926)
Refs #3923
2024-02-20 08:36:08 +00:00
ssongliu 319c21fc36 style: 修改备份账号提示信息 (#3925) 2024-02-20 08:18:08 +00:00
ssongliu e346442d36 fix: 解决备份账号异常导致备份记录无法删除的问题 (#3922)
Refs #3919
2024-02-20 07:02:07 +00:00
ALMAS 1781362342 pref: 优化应用安装表单选择器组件支持多选 (#3918) 2024-02-20 14:58:58 +08:00
wanghe-fit2cloud 3035bb2bf6 chore: Fix typos 2024-02-19 18:02:09 +08:00
maninhill b896a5be3b Update README.md 2024-02-19 17:20:56 +08:00
maninhill d51abd529a Update README.md 2024-02-19 17:19:57 +08:00
zhengkunwang 550eed7962 feat: 升级依赖 (#3907) 2024-02-19 05:55:27 +00:00
mobeicanyue 51d09b7164 Fix typos (#3897)
* Fix typos in backend folder

* Fix typos in frontend

* Fix typos in frontend

* Fix Percent typo
2024-02-17 22:42:09 +08:00
moonrailgun 046d8f526b fix: fix typo for i18n-en (#3887) 2024-02-15 23:12:23 +08:00
zhengkunwang 5bf121b687 feat: 限制文件最大上传个数 (#3867) 2024-02-09 01:52:09 +00:00
zhengkunwang e6a43d22c8 feat: 优化应用状态同步 (#3866) 2024-02-09 01:50:11 +00:00
zhengkunwang 3fd7aa17a8 fix: 解决拖拽上传文件夹文件上传不全的问题 (#3865)
Refs https://github.com/1Panel-dev/1Panel/issues/3848
2024-02-08 08:00:09 +00:00
ssongliu 6a903b7fdf fix: 解决容器监控单位不正确的问题 (#3857)
Refs #3853
2024-02-07 14:30:09 +00:00
ssongliu 958f9a1aad fix: 修改容器镜像仓库正则表达式 (#3856)
Refs #3851
2024-02-07 14:28:08 +00:00
zhengkunwang e48830cd99 feat: 升级 Go 版本到 1.21 (#3847) 2024-02-06 08:36:13 +00:00
ssongliu ece2cb433d style: 系统快照恢复样式调整 (#3845) 2024-02-06 06:20:14 +00:00
zhengkunwang fb06e29aa3 feat: Nginx 配置支持 Lua 模块 (#3844) 2024-02-06 06:02:12 +00:00
ssongliu 4fc26a3061 fix: 解决升级后备份数据未同步的问题 (#3837) 2024-02-05 17:56:47 +08:00
ssongliu d8669b90bf feat: WebDAV 支持连接 Alist (#3836)
Refs #3495
2024-02-05 09:28:12 +00:00
ssongliu 9e980e8e0c feat: SFTP Client 增加可连接性校验 (#3834) 2024-02-05 07:54:13 +00:00
zhengkunwang 140cd564da feat: 适配多容器应用 (#3832) 2024-02-05 07:00:13 +00:00
ssongliu b40087c88c fix: 解决版本升级时,过滤备份失败的备份文件 (#3831)
Refs #3826
2024-02-05 06:24:21 +00:00
ssongliu 8e620172e6 fix: 解决删除备份文件路径错误的问题 (#3829) 2024-02-05 10:18:43 +08:00
ssongliu 9b8b9f505b fix: 修改计划任务执行周期校验 (#3821) 2024-02-04 14:50:38 +00:00
ssongliu 1169648162 feat: 面板开启 https 时,cookie 开启 secure 属性 (#3817) 2024-02-04 22:46:33 +08:00
zhengkunwang c169f211a0 feat: 限制版本为 latest 应用升级 (#3816) 2024-02-04 10:15:41 +00:00
zhengkunwang eef445c1eb style: 应用忽略升级样式修改 (#3809) 2024-02-04 04:02:54 +00:00
zhengkunwang bedb8ce286 style: 修改应用详情页面样式 (#3808) 2024-02-04 04:01:14 +00:00
ssongliu 3652c46ac9 fix: 解决移动设备下,容器名称丢失的问题 (#3805)
Refs #3798
2024-02-04 03:58:52 +00:00
ssongliu 0449868d3b fix: 修改同步快照过程中的名称校验 (#3797) 2024-02-03 05:14:22 +00:00
318 changed files with 70421 additions and 14888 deletions
+5 -5
View File
@@ -1,8 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: 问题与讨论 / Questions & Discussions
url: https://github.com/1Panel-dev/1Panel/discussions
about: 提出针对本项目安装部署、使用及其他方面的相关问题 / Raise questions about the installation, deployment, use and other aspects of the project.
- name: 对 1Panel 项目有其他问题
url: https://1panel.cn
about: 如果想要进一步了解 1Panel 项目,欢迎发送邮件到 support@fit2cloud.com 进行提问。
- name: 反馈安全问题 (Report Security Bug)
url: mailto://support@fit2cloud.com
about: 请通过邮箱 support@fit2cloud.com 反馈安全问题 (Please report security vulnerabilities to support@fit2cloud.com)
url: https://bbs.fit2cloud.com/c/1p/7
about: 如果你对 1Panel 有其他想要提问的,我们欢迎到我们的官方社区进行提问。
-12
View File
@@ -1,12 +0,0 @@
name: 问题咨询
description: 提出针对本项目安装部署、使用及其他方面的相关问题
title: "[QUESTION]"
labels: question
assignees: wanghe-fit2cloud
body:
- type: textarea
id: description
attributes:
label: "请描述您的问题"
validations:
required: true
+2 -2
View File
@@ -15,7 +15,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: '18.14'
node-version: '20.2'
- name: Build Web
run: |
cd frontend && npm install && npm run build:pro
@@ -24,7 +24,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: '1.20.x'
go-version: '1.21.x'
- name: Build Release
uses: goreleaser/goreleaser-action@v4
with:
+7
View File
@@ -39,6 +39,13 @@ frontend/auto-imports.d.ts
frontend/components.d.ts
frontend/src/xpack
backend/xpack
backend/router/entry_xpack.go
backend/server/init_xpack.go
plugins/openresty/waf/db
plugins/openresty/waf/data
plugins/openresty/waf/sites
plugins/openresty/waf/conf/websites.json
.history/
dist/
+2 -2
View File
@@ -5,7 +5,7 @@ PR are always welcome, even if they only contain small fixes like typos or a few
Please submit a PR broken down into small changes bit by bit. A PR consisting of a lot features and code changes may be hard to review. It is recommended to submit PRs in an incremental fashion.
This [development guideline](https://kubeoperator.io/docs/dev/dev_manual/) contains information about repository structure, how to setup development environment, how to run it, and more.
This [development guideline](https://1panel.cn/docs/dev_manual/dev_manual/) contains information about repository structure, how to setup development environment, how to run it, and more.
Note: If you split your pull request to small changes, please make sure any of the changes goes to master will not break anything. Otherwise, it can not be merged until this feature complete.
@@ -22,4 +22,4 @@ When reporting issues, always include:
* Snapshots or log files if needed
Because the issues are open to the public, when submitting files, be sure to remove any sensitive information, e.g. user name, password, IP address, and company name. You can
replace those parts with "REDACTED" or other strings like "****".
replace those parts with "REDACTED" or other strings like "****".
+7 -8
View File
@@ -11,13 +11,13 @@
------------------------------
1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。1Panel 的功能和优势包括:
1Panel 是新一代的 Linux 服务器运维管理面板。
- **快速建站**:深度集成 Wordpress 和 [Halo](https://github.com/halo-dev/halo/),域名绑定、SSL 证书配置等一键搞定;
- **高效管理**:通过 Web 端轻松管理 Linux 服务器,包括主机监控、文件管理、数据库管理、容器管理等;
- **安全可靠**:基于容器来管理和部署应用,最小漏洞暴露面,提供防火墙和日志审计等功能;
- **一键备份**:支持一键备份和恢复,备份数据到各类云端存储,永不丢失;
- **应用商店**:精选各类高质量开源工具和应用软件,助力您轻松安装和升级。
- **高效管理**:用户可以通过 Web 图形界面轻松管理 Linux 服务器,实现主机监控、文件管理、数据库管理、容器管理等功能;
- **快速建站**:深度集成开源建站软件 WordPress 和 [Halo](https://github.com/halo-dev/halo/),域名绑定、SSL 证书配置等操作一键搞定;
- **应用商店**:精选上架各类高质量的开源工具和应用软件,协助用户轻松安装并升级;
- **安全可靠**:基于容器管理并部署应用,实现最小的漏洞暴露面,同时提供防火墙和日志审计等功能;
- **一键备份**:支持一键备份和恢复,用户可以将数据备份到各类云端存储介质,永不丢失。
## UI 展示
@@ -50,8 +50,7 @@ curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_
如果您在使用过程中发现任何安全问题,请通过以下方式直接联系我们:
- 邮箱:support@fit2cloud.com
- 电话:400-052-0755
- 邮箱:wanghe@fit2cloud.com
## Star History
+1 -5
View File
@@ -101,11 +101,7 @@ func (b *BaseApi) LoadRecordLog(c *gin.Context) {
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
content, err := cronjobService.LoadRecordLog(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
content := cronjobService.LoadRecordLog(req)
helper.SuccessWithData(c, content)
}
+16
View File
@@ -8,6 +8,22 @@ import (
"github.com/gin-gonic/gin"
)
// @Tags Dashboard
// @Summary Load os info
// @Description 获取服务器基础数据
// @Accept json
// @Success 200 {object} dto.OsInfo
// @Security ApiKeyAuth
// @Router /dashboard/base/os [get]
func (b *BaseApi) LoadDashboardOsInfo(c *gin.Context) {
data, err := dashboardService.LoadOsInfo()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags Dashboard
// @Summary Load dashboard base info
// @Description 获取首页基础数据
-18
View File
@@ -164,24 +164,6 @@ func (b *BaseApi) SearchPostgresql(c *gin.Context) {
})
}
// @Tags Database Postgresql
// @Summary List postgresql database names
// @Description 获取 postgresql 数据库列表
// @Accept json
// @Param request body dto.PageInfo true "request"
// @Success 200 {array} dto.PostgresqlOption
// @Security ApiKeyAuth
// @Router /databases/pg/options [get]
func (b *BaseApi) ListPostgresqlDBName(c *gin.Context) {
list, err := postgresqlService.ListDBOption()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, list)
}
// @Tags Database Postgresql
// @Summary Load postgresql database from remote
// @Description 从服务器获取
+16
View File
@@ -203,3 +203,19 @@ func (b *BaseApi) OperateNodeModules(c *gin.Context) {
}
helper.SuccessWithOutData(c)
}
// @Tags Runtime
// @Summary Sync runtime status
// @Description 同步运行环境状态
// @Accept json
// @Success 200
// @Security ApiKeyAuth
// @Router /runtimes/sync [post]
func (b *BaseApi) SyncStatus(c *gin.Context) {
err := runtimeService.SyncRuntimeStatus()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
+2 -2
View File
@@ -38,8 +38,8 @@ type ContainerInfo struct {
}
type ResourceLimit struct {
CPU int `json:"cpu"`
Memory int `json:"memory"`
CPU int `json:"cpu"`
Memory uint64 `json:"memory"`
}
type ContainerOperate struct {
+8
View File
@@ -24,6 +24,14 @@ type DashboardBase struct {
CurrentInfo DashboardCurrent `json:"currentInfo"`
}
type OsInfo struct {
OS string `json:"os"`
Platform string `json:"platform"`
PlatformFamily string `json:"platformFamily"`
KernelArch string `json:"kernelArch"`
KernelVersion string `json:"kernelVersion"`
}
type DashboardCurrent struct {
Uptime uint64 `json:"uptime"`
TimeSinceUptime string `json:"timeSinceUptime"`
+1 -1
View File
@@ -41,7 +41,7 @@ type MysqlDBInfo struct {
Username string `json:"username"`
Password string `json:"password"`
Permission string `json:"permission"`
BackupCount int `json:"backupCount"`
IsDelete bool `json:"isDelete"`
Description string `json:"description"`
}
+1 -1
View File
@@ -20,7 +20,7 @@ type PostgresqlDBInfo struct {
Username string `json:"username"`
Password string `json:"password"`
SuperUser bool `json:"superUser"`
BackupCount int `json:"backupCount"`
IsDelete bool `json:"isDelete"`
Description string `json:"description"`
}
+1 -1
View File
@@ -64,7 +64,7 @@ type FileDeCompress struct {
type FileEdit struct {
Path string `json:"path" validate:"required"`
Content string `json:"content" validate:"required"`
Content string `json:"content"`
}
type FileRename struct {
+2
View File
@@ -52,6 +52,8 @@ type SettingInfo struct {
AppStoreSyncStatus string `json:"appStoreSyncStatus"`
FileRecycleBin string `json:"fileRecycleBin"`
SnapshotIgnore string `json:"snapshotIgnore"`
}
type SettingUpdate struct {
+1
View File
@@ -9,5 +9,6 @@ type DatabaseMysql struct {
Username string `json:"username" gorm:"type:varchar(256);not null"`
Password string `json:"password" gorm:"type:varchar(256);not null"`
Permission string `json:"permission" gorm:"type:varchar(256);not null"`
IsDelete bool `json:"isDelete" gorm:"type:varchar(64)"`
Description string `json:"description" gorm:"type:varchar(256);"`
}
+2 -1
View File
@@ -8,6 +8,7 @@ type DatabasePostgresql struct {
Format string `json:"format" gorm:"type:varchar(64);not null"`
Username string `json:"username" gorm:"type:varchar(256);not null"`
Password string `json:"password" gorm:"type:varchar(256);not null"`
SuperUser bool `json:"superUser" gorm:"type:varchar(64)" `
SuperUser bool `json:"superUser" gorm:"type:varchar(64)"`
IsDelete bool `json:"isDelete" gorm:"type:varchar(64)"`
Description string `json:"description" gorm:"type:varchar(256);"`
}
+9 -2
View File
@@ -18,6 +18,7 @@ type IWebsiteRepo interface {
WithDefaultServer() DBOption
WithDomainLike(domain string) DBOption
WithRuntimeID(runtimeID uint) DBOption
WithIDs(ids []uint) DBOption
Page(page, size int, opts ...DBOption) (int64, []model.Website, error)
List(opts ...DBOption) ([]model.Website, error)
GetFirst(opts ...DBOption) (model.Website, error)
@@ -36,9 +37,15 @@ func NewIWebsiteRepo() IWebsiteRepo {
type WebsiteRepo struct {
}
func (w *WebsiteRepo) WithAppInstallId(appInstallId uint) DBOption {
func (w *WebsiteRepo) WithAppInstallId(appInstallID uint) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("app_install_id = ?", appInstallId)
return db.Where("app_install_id = ?", appInstallID)
}
}
func (w *WebsiteRepo) WithIDs(ids []uint) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("id in (?)", ids)
}
}
+6
View File
@@ -14,6 +14,7 @@ type IWebsiteDomainRepo interface {
WithWebsiteId(websiteId uint) DBOption
WithPort(port int) DBOption
WithDomain(domain string) DBOption
WithDomainLike(domain string) DBOption
Page(page, size int, opts ...DBOption) (int64, []model.WebsiteDomain, error)
GetFirst(opts ...DBOption) (model.WebsiteDomain, error)
GetBy(opts ...DBOption) ([]model.WebsiteDomain, error)
@@ -44,6 +45,11 @@ func (w WebsiteDomainRepo) WithDomain(domain string) DBOption {
return db.Where("domain = ?", domain)
}
}
func (w WebsiteDomainRepo) WithDomainLike(domain string) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("domain like ?", "%"+domain+"%")
}
}
func (w WebsiteDomainRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteDomain, error) {
var domains []model.WebsiteDomain
db := getDb(opts...).Model(&model.WebsiteDomain{})
+1 -1
View File
@@ -397,7 +397,7 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
}
index++
}
if app.Limit == 0 && appInstall.Name != serviceName {
if app.Limit == 0 && appInstall.Name != serviceName && len(servicesMap) == 1 {
servicesMap[appInstall.Name] = servicesMap[serviceName]
delete(servicesMap, serviceName)
serviceName = appInstall.Name
+85 -85
View File
@@ -12,8 +12,6 @@ import (
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"gopkg.in/yaml.v3"
@@ -152,7 +150,7 @@ func (a *AppInstallService) CheckExist(req request.AppInstalledInfo) (*response.
if reflect.DeepEqual(appInstall, model.AppInstall{}) {
return res, nil
}
if err = syncByID(appInstall.ID); err != nil {
if err = syncAppInstallStatus(&appInstall); err != nil {
return nil, err
}
@@ -242,26 +240,26 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
if err != nil {
return handleErr(install, err, out)
}
return syncByID(install.ID)
return syncAppInstallStatus(&install)
case constant.Stop:
out, err := compose.Stop(dockerComposePath)
if err != nil {
return handleErr(install, err, out)
}
return syncByID(install.ID)
return syncAppInstallStatus(&install)
case constant.Restart:
out, err := compose.Restart(dockerComposePath)
if err != nil {
return handleErr(install, err, out)
}
return syncByID(install.ID)
return syncAppInstallStatus(&install)
case constant.Delete:
if err := deleteAppInstall(install, req.DeleteBackup, req.ForceDelete, req.DeleteDB); err != nil && !req.ForceDelete {
return err
}
return nil
case constant.Sync:
return syncByID(install.ID)
return syncAppInstallStatus(&install)
case constant.Upgrade:
return upgradeInstall(install.ID, req.DetailId, req.Backup, req.PullImage)
case constant.Reload:
@@ -413,7 +411,7 @@ func (a *AppInstallService) SyncAll(systemInit bool) error {
return err
}
for _, i := range allList {
if i.Status == constant.Installing || i.Status == constant.Upgrading {
if i.Status == constant.Installing || i.Status == constant.Upgrading || i.Status == constant.Rebuilding {
if systemInit {
i.Status = constant.Error
i.Message = "1Panel restart causes the task to terminate"
@@ -422,7 +420,7 @@ func (a *AppInstallService) SyncAll(systemInit bool) error {
continue
}
if !systemInit {
if err := syncByID(i.ID); err != nil {
if err = syncAppInstallStatus(&i); err != nil {
global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error())
}
}
@@ -529,7 +527,7 @@ func (a *AppInstallService) ChangeAppPort(req request.PortUpdate) error {
return nil
}
if err := updateInstallInfoInDB(req.Key, req.Name, "port", true, strconv.FormatInt(req.Port, 10)); err != nil {
if err := updateInstallInfoInDB(req.Key, req.Name, "port", strconv.FormatInt(req.Port, 10)); err != nil {
return nil
}
@@ -641,10 +639,11 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
for _, form := range appForm.FormFields {
if v, ok := envs[form.EnvKey]; ok {
appParam := response.AppParam{
Edit: false,
Key: form.EnvKey,
Rule: form.Rule,
Type: form.Type,
Edit: false,
Key: form.EnvKey,
Rule: form.Rule,
Type: form.Type,
Multiple: form.Multiple,
}
if form.Edit {
appParam.Edit = true
@@ -656,10 +655,20 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithServiceName(v.(string)))
appParam.ShowValue = appInstall.Name
} else if form.Type == "select" {
for _, fv := range form.Values {
if fv.Value == v {
appParam.ShowValue = fv.Label
break
if form.Multiple {
if v == "" {
appParam.Value = []string{}
} else {
if str, ok := v.(string); ok {
appParam.Value = strings.Split(str, ",")
}
}
} else {
for _, fv := range form.Values {
if fv.Value == v {
appParam.ShowValue = fv.Label
break
}
}
}
appParam.Values = form.Values
@@ -667,14 +676,15 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
params = append(params, appParam)
} else {
params = append(params, response.AppParam{
Edit: form.Edit,
Key: form.EnvKey,
Rule: form.Rule,
Type: form.Type,
LabelZh: form.LabelZh,
LabelEn: form.LabelEn,
Value: form.Default,
Values: form.Values,
Edit: form.Edit,
Key: form.EnvKey,
Rule: form.Rule,
Type: form.Type,
LabelZh: form.LabelZh,
LabelEn: form.LabelEn,
Value: form.Default,
Values: form.Values,
Multiple: form.Multiple,
})
}
}
@@ -690,15 +700,11 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
return &res, nil
}
func syncByID(installID uint) error {
appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(installID))
if err != nil {
return err
}
if appInstall.Status == constant.Installing {
func syncAppInstallStatus(appInstall *model.AppInstall) error {
if appInstall.Status == constant.Installing || appInstall.Status == constant.Rebuilding || appInstall.Status == constant.Upgrading {
return nil
}
containerNames, err := getContainerNames(appInstall)
containerNames, err := getContainerNames(*appInstall)
if err != nil {
return err
}
@@ -710,75 +716,69 @@ func syncByID(installID uint) error {
if err != nil {
return err
}
var (
errorContainers []string
notFoundContainers []string
runningContainers []string
exitedContainers []string
runningCount int
exitedCount int
pausedCount int
exitedContainerNames []string
total = len(containerNames)
count = len(containers)
)
for _, n := range containers {
switch n.State {
foundNames := make(map[string]bool)
for _, con := range containers {
foundNames[con.Names[0]] = true
switch con.State {
case "exited":
exitedContainers = append(exitedContainers, n.Names[0])
exitedContainerNames = append(exitedContainerNames, strings.TrimPrefix(con.Names[0], "/"))
exitedCount++
case "running":
runningContainers = append(runningContainers, n.Names[0])
default:
errorContainers = append(errorContainers, n.Names[0])
runningCount++
case "paused":
pausedCount++
}
}
var notFoundNames []string
for _, name := range containerNames {
exist := false
for _, container := range containers {
if common.ExistWithStrArray(name, container.Names) {
exist = true
break
}
}
if !exist {
notFoundContainers = append(notFoundContainers, name)
if !foundNames["/"+name] {
notFoundNames = append(notFoundNames, name)
}
}
containerCount := len(containers)
errCount := len(errorContainers)
notFoundCount := len(notFoundContainers)
existedCount := len(exitedContainers)
normalCount := len(containerNames)
runningCount := len(runningContainers)
if containerCount == 0 {
appInstall.Status = constant.Error
appInstall.Message = i18n.GetMsgWithMap("ErrContainerNotFound", map[string]interface{}{"name": strings.Join(containerNames, ",")})
return appInstallRepo.Save(context.Background(), &appInstall)
}
if errCount == 0 && existedCount == 0 && notFoundCount == 0 {
appInstall.Status = constant.Running
return appInstallRepo.Save(context.Background(), &appInstall)
}
if existedCount == normalCount {
switch {
case count == 0:
if appInstall.Status != constant.Error {
appInstall.Status = constant.SyncErr
appInstall.Message = buserr.WithName("ErrContainerNotFound", strings.Join(containerNames, ",")).Error()
}
case exitedCount == total:
appInstall.Status = constant.Stopped
return appInstallRepo.Save(context.Background(), &appInstall)
}
if errCount == normalCount {
appInstall.Status = constant.Error
}
if runningCount < normalCount {
case runningCount == total:
appInstall.Status = constant.Running
case pausedCount == total:
appInstall.Status = constant.Paused
default:
var msg string
if exitedCount > 0 {
msg = buserr.WithName("ErrContainerMsg", strings.Join(exitedContainerNames, ",")).Error()
}
if len(notFoundNames) > 0 {
msg += buserr.WithName("ErrContainerNotFound", strings.Join(notFoundNames, ",")).Error()
}
if msg == "" {
msg = buserr.New("ErrAppWarn").Error()
}
appInstall.Message = msg
appInstall.Status = constant.UnHealthy
}
_ = appInstallRepo.Save(context.Background(), appInstall)
var errMsg string
if errCount > 0 {
errMsg += i18n.GetMsgWithMap("ErrContainerMsg", map[string]interface{}{"name": strings.Join(errorContainers, ",")})
}
if notFoundCount > 0 {
errMsg += i18n.GetMsgWithMap("ErrContainerNotFound", map[string]interface{}{"name": strings.Join(notFoundContainers, ",")})
}
appInstall.Message = errMsg
return appInstallRepo.Save(context.Background(), &appInstall)
return nil
}
func updateInstallInfoInDB(appKey, appName, param string, isRestart bool, value interface{}) error {
func updateInstallInfoInDB(appKey, appName, param string, value interface{}) error {
if param != "password" && param != "port" && param != "user-password" {
return nil
}
+74 -30
View File
@@ -354,8 +354,8 @@ func deleteAppInstall(install model.AppInstall, deleteBackup bool, forceDelete b
websiteApp, _ := appRepo.GetFirst(commonRepo.WithByID(websiteAppInstall.AppId))
if websiteApp.Type == constant.RuntimePHP {
go func() {
_, _ = compose.Down(install.GetComposePath())
_ = op.DeleteDir(install.GetPath())
_, _ = compose.Down(websiteAppInstall.GetComposePath())
_ = op.DeleteDir(websiteAppInstall.GetPath())
}()
_ = appInstallRepo.Delete(ctx, websiteAppInstall)
}
@@ -687,6 +687,12 @@ func handleMap(params map[string]interface{}, envParams map[string]string) {
envParams[k] = strconv.Itoa(int(t))
case int:
envParams[k] = strconv.Itoa(t)
case []interface{}:
strArray := make([]string, len(t))
for i := range t {
strArray[i] = strings.ToLower(fmt.Sprintf("%v", t[i]))
}
envParams[k] = strings.Join(strArray, ",")
default:
envParams[k] = t.(string)
}
@@ -843,38 +849,34 @@ func checkContainerNameIsExist(containerName, appDir string) (bool, error) {
func upApp(appInstall *model.AppInstall, pullImages bool) {
upProject := func(appInstall *model.AppInstall) (err error) {
if err == nil {
var (
out string
errMsg string
)
if pullImages && appInstall.App.Type != "php" {
out, err = compose.Pull(appInstall.GetComposePath())
if err != nil {
if out != "" {
if strings.Contains(out, "no such host") {
errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":"
}
if strings.Contains(out, "timeout") {
errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut") + ":"
}
appInstall.Message = errMsg + out
}
return err
}
}
out, err = compose.Up(appInstall.GetComposePath())
var (
out string
errMsg string
)
if pullImages && appInstall.App.Type != "php" {
out, err = compose.Pull(appInstall.GetComposePath())
if err != nil {
if out != "" {
if strings.Contains(out, "no such host") {
errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":"
}
if strings.Contains(out, "timeout") {
errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut") + ":"
}
appInstall.Message = errMsg + out
}
return err
}
return
} else {
return
}
out, err = compose.Up(appInstall.GetComposePath())
if err != nil {
if out != "" {
appInstall.Message = errMsg + out
}
return err
}
return
}
if err := upProject(appInstall); err != nil {
appInstall.Status = constant.Error
@@ -897,11 +899,12 @@ func rebuildApp(appInstall model.AppInstall) error {
_ = handleErr(appInstall, err, out)
return
}
out, err = compose.Up(dockerComposePath)
out, err = compose.Up(appInstall.GetComposePath())
if err != nil {
_ = handleErr(appInstall, err, out)
return
}
appInstall.Status = constant.Running
_ = appInstallRepo.Save(context.Background(), &appInstall)
}()
@@ -989,6 +992,43 @@ func handleLocalAppDetail(versionDir string, appDetail *model.AppDetail) error {
if err != nil {
return buserr.WithMap(constant.ErrFileParseApp, map[string]interface{}{"name": "data.yml", "err": err.Error()}, err)
}
var appParam dto.AppForm
if err = json.Unmarshal(dataJson, &appParam); err != nil {
return buserr.WithMap(constant.ErrFileParseApp, map[string]interface{}{"name": "data.yml", "err": err.Error()}, err)
}
for _, formField := range appParam.FormFields {
if strings.Contains(formField.EnvKey, " ") {
return buserr.WithName(constant.ErrAppParamKey, formField.EnvKey)
}
}
var dataMap map[string]interface{}
err = yaml.Unmarshal(paramByte, &dataMap)
if err != nil {
return buserr.WithMap(constant.ErrFileParseApp, map[string]interface{}{"name": "data.yml", "err": err.Error()}, err)
}
additionalProperties, ok := dataMap["additionalProperties"].(map[string]interface{})
if !ok {
return buserr.WithName(constant.ErrAppParamKey, "additionalProperties")
}
formFieldsInterface, ok := additionalProperties["formFields"]
if ok {
formFields, ok := formFieldsInterface.([]interface{})
if !ok {
return buserr.WithName(constant.ErrAppParamKey, "formFields")
}
for _, item := range formFields {
field := item.(map[string]interface{})
for key, value := range field {
if value == nil {
return buserr.WithName(constant.ErrAppParamKey, key)
}
}
}
}
appDetail.Params = string(dataJson)
return nil
}
@@ -1040,8 +1080,8 @@ func handleErr(install model.AppInstall, err error, out string) error {
if out != "" {
install.Message = out
reErr = errors.New(out)
install.Status = constant.Error
}
install.Status = constant.Error
_ = appInstallRepo.Save(context.Background(), &install)
return reErr
}
@@ -1052,6 +1092,10 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]respons
if updated && (installed.App.Type == "php" || installed.Status == constant.Installing || (installed.App.Key == constant.AppMysql && installed.Version == "5.6.51")) {
continue
}
if err := syncAppInstallStatus(&installed); err != nil {
global.LOG.Error("sync app install status error : ", err)
}
installDTO := response.AppInstalledDTO{
AppInstall: installed,
Path: installed.GetPath(),
@@ -1066,7 +1110,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]respons
}
var versions []string
for _, detail := range details {
if detail.IgnoreUpgrade {
if detail.IgnoreUpgrade || installed.Version == "latest" {
continue
}
if common.IsCrossVersion(installed.Version, detail.Version) && !app.CrossVersionUpdate {
+10 -2
View File
@@ -109,6 +109,10 @@ func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) (
if err != nil {
return nil, err
}
httpsSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
if err != nil {
return nil, err
}
lifeTime, err := strconv.Atoi(setting.Value)
if err != nil {
return nil, err
@@ -129,7 +133,7 @@ func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) (
sessionUser, err := global.SESSION.Get(sID)
if err != nil {
sID = uuid.New().String()
c.SetCookie(constant.SessionName, sID, 0, "", "", false, false)
c.SetCookie(constant.SessionName, sID, 0, "", "", httpsSetting.Value == "enable", true)
err := global.SESSION.Set(sID, sessionUser, lifeTime)
if err != nil {
return nil, err
@@ -144,9 +148,13 @@ func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) (
}
func (u *AuthService) LogOut(c *gin.Context) error {
httpsSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
if err != nil {
return err
}
sID, _ := c.Cookie(constant.SessionName)
if sID != "" {
c.SetCookie(constant.SessionName, sID, -1, "", "", false, false)
c.SetCookie(constant.SessionName, sID, -1, "", "", httpsSetting.Value == "enable", true)
err := global.SESSION.Delete(sID)
if err != nil {
return err
+14 -16
View File
@@ -266,22 +266,18 @@ func (u *BackupService) BatchDeleteRecord(ids []uint) error {
return err
}
for _, record := range records {
if record.Source == "LOCAL" {
if err := os.Remove(record.FileDir + "/" + record.FileName); err != nil {
global.LOG.Errorf("remove file %s failed, err: %v", record.FileDir+record.FileName, err)
}
} else {
backupAccount, err := backupRepo.Get(commonRepo.WithByType(record.Source))
if err != nil {
return err
}
client, err := u.NewClient(&backupAccount)
if err != nil {
return err
}
if _, err = client.Delete(record.FileDir + record.FileName); err != nil {
global.LOG.Errorf("remove file %s from %s failed, err: %v", record.FileDir+record.FileName, record.Source, err)
}
backupAccount, err := backupRepo.Get(commonRepo.WithByType(record.Source))
if err != nil {
global.LOG.Errorf("load backup account %s info from db failed, err: %v", record.Source, err)
continue
}
client, err := u.NewClient(&backupAccount)
if err != nil {
global.LOG.Errorf("new client for backup account %s failed, err: %v", record.Source, err)
continue
}
if _, err = client.Delete(path.Join(record.FileDir, record.FileName)); err != nil {
global.LOG.Errorf("remove file %s from %s failed, err: %v", path.Join(record.FileDir, record.FileName), record.Source, err)
}
}
return backupRepo.DeleteRecord(context.Background(), commonRepo.WithIdsIn(ids))
@@ -481,6 +477,8 @@ func (u *BackupService) loadRecordSize(records []model.BackupRecord) ([]dto.Back
datas = append(datas, item)
wg.Done()
}(i)
} else {
datas = append(datas, item)
}
}
wg.Wait()
+1 -1
View File
@@ -115,7 +115,7 @@ func handleAppBackup(install *model.AppInstall, backupDir, fileName string) erro
if err != nil {
return err
}
if err := handleMysqlBackup(db.MysqlName, db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil {
if err := handleMysqlBackup(db.MysqlName, resource.Key, db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil {
return err
}
case constant.AppPostgresql:
+12 -4
View File
@@ -29,7 +29,7 @@ func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
targetDir := path.Join(localDir, itemDir)
fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
if err := handleMysqlBackup(req.Name, req.DetailName, targetDir, fileName); err != nil {
if err := handleMysqlBackup(req.Name, req.Type, req.DetailName, targetDir, fileName); err != nil {
return err
}
@@ -100,18 +100,20 @@ func (u *BackupService) MysqlRecoverByUpload(req dto.CommonRecover) error {
return nil
}
func handleMysqlBackup(database, dbName, targetDir, fileName string) error {
func handleMysqlBackup(database, dbType, dbName, targetDir, fileName string) error {
dbInfo, err := mysqlRepo.Get(commonRepo.WithByName(dbName), mysqlRepo.WithByMysqlName(database))
if err != nil {
return err
}
cli, _, err := LoadMysqlClientByFrom(database)
cli, version, err := LoadMysqlClientByFrom(database)
if err != nil {
return err
}
backupInfo := client.BackupInfo{
Name: dbName,
Type: dbType,
Version: version,
Format: dbInfo.Format,
TargetDir: targetDir,
FileName: fileName,
@@ -134,7 +136,7 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
if err != nil {
return err
}
cli, _, err := LoadMysqlClientByFrom(req.Name)
cli, version, err := LoadMysqlClientByFrom(req.Name)
if err != nil {
return err
}
@@ -143,6 +145,8 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/%s/%s_%s.sql.gz", req.Type, req.DetailName, time.Now().Format("20060102150405")))
if err := cli.Backup(client.BackupInfo{
Name: req.DetailName,
Type: req.Type,
Version: version,
Format: dbInfo.Format,
TargetDir: path.Dir(rollbackFile),
FileName: path.Base(rollbackFile),
@@ -156,6 +160,8 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
global.LOG.Info("recover failed, start to rollback now")
if err := cli.Recover(client.RecoverInfo{
Name: req.DetailName,
Type: req.Type,
Version: version,
Format: dbInfo.Format,
SourceFile: rollbackFile,
@@ -172,6 +178,8 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
}
if err := cli.Recover(client.RecoverInfo{
Name: req.DetailName,
Type: req.Type,
Version: version,
Format: dbInfo.Format,
SourceFile: req.File,
+7 -7
View File
@@ -5,7 +5,6 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/pkg/errors"
"io"
"os"
"os/exec"
@@ -18,6 +17,8 @@ import (
"time"
"unicode/utf8"
"github.com/pkg/errors"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
@@ -336,7 +337,7 @@ func (u *ContainerService) LoadResourceLimit() (*dto.ResourceLimit, error) {
data := dto.ResourceLimit{
CPU: cpuCounts,
Memory: int(memoryInfo.Total),
Memory: memoryInfo.Total,
}
return &data, nil
}
@@ -434,7 +435,7 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
data.AutoRemove = oldContainer.HostConfig.AutoRemove
data.Privileged = oldContainer.HostConfig.Privileged
data.PublishAllPorts = oldContainer.HostConfig.PublishAllPorts
data.RestartPolicy = oldContainer.HostConfig.RestartPolicy.Name
data.RestartPolicy = string(oldContainer.HostConfig.RestartPolicy.Name)
if oldContainer.HostConfig.NanoCPUs != 0 {
data.NanoCPUs = float64(oldContainer.HostConfig.NanoCPUs) / 1000000000
}
@@ -683,7 +684,6 @@ func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, containerType,
global.LOG.Errorf("read bytes from log failed, err: %v", err)
continue
}
// check if the bytes is valid utf8
if !utf8.Valid(buffer[:n]) {
continue
}
@@ -723,7 +723,6 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error
if cache, ok := stats.MemoryStats.Stats["cache"]; ok {
data.Cache = float64(cache) / 1024 / 1024
}
data.Memory = data.Memory - data.Cache
data.NetworkRX, data.NetworkTX = calculateNetwork(stats.Networks)
data.ShotTime = stats.Read
return &data, nil
@@ -791,7 +790,7 @@ func calculateCPUPercentUnix(stats *types.StatsJSON) float64 {
}
func calculateMemPercentUnix(memStats types.MemoryStats) float64 {
memPercent := 0.0
memUsage := float64(memStats.Usage - memStats.Stats["cache"])
memUsage := float64(memStats.Usage)
memLimit := float64(memStats.Limit)
if memUsage > 0.0 && memLimit > 0.0 {
memPercent = (memUsage / memLimit) * 100.0
@@ -895,6 +894,7 @@ func loadCpuAndMem(client *client.Client, container string) dto.ContainerListSta
data.MemoryCache = stats.MemoryStats.Stats["cache"]
data.MemoryUsage = stats.MemoryStats.Usage
data.MemoryLimit = stats.MemoryStats.Limit
data.MemoryPercent = calculateMemPercentUnix(stats.MemoryStats)
return data
}
@@ -985,7 +985,7 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *types
hostConf.AutoRemove = req.AutoRemove
hostConf.CPUShares = req.CPUShares
hostConf.PublishAllPorts = req.PublishAllPorts
hostConf.RestartPolicy = container.RestartPolicy{Name: req.RestartPolicy}
hostConf.RestartPolicy = container.RestartPolicy{Name: container.RestartPolicyMode(req.RestartPolicy)}
if req.RestartPolicy == "on-failure" {
hostConf.RestartPolicy.MaximumRetryCount = 5
}
+6 -7
View File
@@ -11,7 +11,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/jinzhu/copier"
@@ -33,7 +32,7 @@ type ICronjobService interface {
StartJob(cronjob *model.Cronjob) (string, error)
CleanRecord(req dto.CronjobClean) error
LoadRecordLog(req dto.OperateByID) (string, error)
LoadRecordLog(req dto.OperateByID) string
}
func NewICronjobService() ICronjobService {
@@ -77,19 +76,19 @@ func (u *CronjobService) SearchRecords(search dto.SearchRecord) (int64, interfac
return total, dtoCronjobs, err
}
func (u *CronjobService) LoadRecordLog(req dto.OperateByID) (string, error) {
func (u *CronjobService) LoadRecordLog(req dto.OperateByID) string {
record, err := cronjobRepo.GetRecord(commonRepo.WithByID(req.ID))
if err != nil {
return "", err
return ""
}
if _, err := os.Stat(record.Records); err != nil {
return "", buserr.New("ErrHttpReqNotFound")
return ""
}
content, err := os.ReadFile(record.Records)
if err != nil {
return "", err
return ""
}
return string(content), nil
return string(content)
}
func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
+1 -1
View File
@@ -109,7 +109,7 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Ti
backupDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/%s/%s/%s", dbInfo.DBType, record.Name, dbInfo.Name))
record.FileName = fmt.Sprintf("db_%s_%s.sql.gz", dbInfo.Name, startTime.Format("20060102150405")+common.RandStrAndNum(5))
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
if err := handleMysqlBackup(dbInfo.Database, dbInfo.Name, backupDir, record.FileName); err != nil {
if err := handleMysqlBackup(dbInfo.Database, dbInfo.DBType, dbInfo.Name, backupDir, record.FileName); err != nil {
return err
}
} else {
+4 -1
View File
@@ -82,7 +82,10 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
}
if err != nil {
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), string(message))
if len(message) != 0 {
record.Records, _ = mkdirAndWriteFile(cronjob, record.StartTime, message)
}
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
return
}
if len(message) != 0 {
+26 -1
View File
@@ -22,6 +22,7 @@ import (
type DashboardService struct{}
type IDashboardService interface {
LoadOsInfo() (*dto.OsInfo, error)
LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error)
LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent
@@ -49,6 +50,27 @@ func (u *DashboardService) Restart(operation string) error {
return nil
}
func (u *DashboardService) LoadOsInfo() (*dto.OsInfo, error) {
var baseInfo dto.OsInfo
hostInfo, err := host.Info()
if err != nil {
return nil, err
}
baseInfo.OS = hostInfo.OS
baseInfo.Platform = hostInfo.Platform
baseInfo.PlatformFamily = hostInfo.PlatformFamily
baseInfo.KernelArch = hostInfo.KernelArch
baseInfo.KernelVersion = hostInfo.KernelVersion
if baseInfo.KernelArch == "armv7l" {
baseInfo.KernelArch = "armv7"
}
if baseInfo.KernelArch == "x86_64" {
baseInfo.KernelArch = "amd64"
}
return &baseInfo, nil
}
func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error) {
var baseInfo dto.DashboardBase
hostInfo, err := host.Info()
@@ -183,7 +205,10 @@ func loadDiskInfo() []dto.DiskInfo {
var datas []dto.DiskInfo
stdout, err := cmd.ExecWithTimeOut("df -hT -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev", 2*time.Second)
if err != nil {
return datas
stdout, err = cmd.ExecWithTimeOut("df -lhT -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev", 1*time.Second)
if err != nil {
return datas
}
}
lines := strings.Split(stdout, "\n")
+9 -4
View File
@@ -190,11 +190,13 @@ func (u *MysqlService) LoadFromRemote(req dto.MysqlLoadDB) error {
if err != nil {
return err
}
deleteList := databases
for _, data := range datas {
hasOld := false
for _, oldData := range databases {
if strings.EqualFold(oldData.Name, data.Name) && strings.EqualFold(oldData.MysqlName, data.MysqlName) {
for i := 0; i < len(databases); i++ {
if strings.EqualFold(databases[i].Name, data.Name) && strings.EqualFold(databases[i].MysqlName, data.MysqlName) {
hasOld = true
deleteList = append(deleteList[:i], deleteList[i+1:]...)
break
}
}
@@ -208,6 +210,9 @@ func (u *MysqlService) LoadFromRemote(req dto.MysqlLoadDB) error {
}
}
}
for _, delItem := range deleteList {
_ = mysqlRepo.Update(delItem.ID, map[string]interface{}{"is_delete": true})
}
return nil
}
@@ -342,7 +347,7 @@ func (u *MysqlService) ChangePassword(req dto.ChangeDBInfo) error {
}
global.LOG.Infof("start to update mysql password used by app %s-%s", appModel.Key, appInstall.Name)
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", true, req.Value); err != nil {
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", req.Value); err != nil {
return err
}
}
@@ -355,7 +360,7 @@ func (u *MysqlService) ChangePassword(req dto.ChangeDBInfo) error {
return nil
}
if err := updateInstallInfoInDB(req.Type, req.Database, "password", false, req.Value); err != nil {
if err := updateInstallInfoInDB(req.Type, req.Database, "password", req.Value); err != nil {
return err
}
return nil
+9 -4
View File
@@ -216,11 +216,13 @@ func (u *PostgresqlService) LoadFromRemote(database string) error {
if err != nil {
return err
}
deleteList := databases
for _, data := range datas {
hasOld := false
for _, oldData := range databases {
if strings.EqualFold(oldData.Name, data.Name) && strings.EqualFold(oldData.PostgresqlName, data.PostgresqlName) {
for i := 0; i < len(databases); i++ {
if strings.EqualFold(databases[i].Name, data.Name) && strings.EqualFold(databases[i].PostgresqlName, data.PostgresqlName) {
hasOld = true
deleteList = append(deleteList[:i], deleteList[i+1:]...)
break
}
}
@@ -234,6 +236,9 @@ func (u *PostgresqlService) LoadFromRemote(database string) error {
}
}
}
for _, delItem := range deleteList {
_ = postgresqlRepo.Update(delItem.ID, map[string]interface{}{"is_delete": true})
}
return nil
}
@@ -393,7 +398,7 @@ func (u *PostgresqlService) ChangePassword(req dto.ChangeDBInfo) error {
}
global.LOG.Infof("start to update postgresql password used by app %s-%s", appModel.Key, appInstall.Name)
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", true, req.Value); err != nil {
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", req.Value); err != nil {
return err
}
}
@@ -406,7 +411,7 @@ func (u *PostgresqlService) ChangePassword(req dto.ChangeDBInfo) error {
return nil
}
if err := updateInstallInfoInDB(req.Type, req.Database, "password", false, req.Value); err != nil {
if err := updateInstallInfoInDB(req.Type, req.Database, "password", req.Value); err != nil {
return err
}
return nil
+2 -2
View File
@@ -55,10 +55,10 @@ func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
}
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
if err := updateInstallInfoInDB("redis", "", "password", true, req.Value); err != nil {
if err := updateInstallInfoInDB("redis", "", "password", req.Value); err != nil {
return err
}
if err := updateInstallInfoInDB("redis-commander", "", "password", true, req.Value); err != nil {
if err := updateInstallInfoInDB("redis-commander", "", "password", req.Value); err != nil {
return err
}
+1 -1
View File
@@ -181,7 +181,7 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
} else {
daemonMap["iptables"] = false
}
case "Dirver":
case "Driver":
if opts, ok := daemonMap["exec-opts"]; ok {
if optsValue, isArray := opts.([]interface{}); isArray {
for i := 0; i < len(optsValue); i++ {
+19 -6
View File
@@ -37,7 +37,10 @@ func getNginxFull(website *model.Website) (dto.NginxFull, error) {
if err != nil {
return nginxFull, err
}
config := parser.NewStringParser(string(content)).Parse()
config, err := parser.NewStringParser(string(content)).Parse()
if err != nil {
return dto.NginxFull{}, err
}
config.FilePath = nginxConfig.FilePath
nginxConfig.OldContent = string(content)
nginxConfig.Config = config
@@ -54,7 +57,10 @@ func getNginxFull(website *model.Website) (dto.NginxFull, error) {
if err != nil {
return nginxFull, err
}
siteConfig := parser.NewStringParser(string(siteNginxContent)).Parse()
siteConfig, err := parser.NewStringParser(string(siteNginxContent)).Parse()
if err != nil {
return dto.NginxFull{}, err
}
siteConfig.FilePath = siteConfigPath
siteNginxConfig.Config = siteConfig
siteNginxConfig.OldContent = string(siteNginxContent)
@@ -156,15 +162,22 @@ func deleteNginxConfig(scope string, params []dto.NginxParam, website *model.Web
}
func getNginxParamsFromStaticFile(scope dto.NginxKey, newParams []dto.NginxParam) []dto.NginxParam {
newConfig := &components.Config{}
var (
newConfig = &components.Config{}
err error
)
updateScope := "in"
switch scope {
case dto.SSL:
newConfig = parser.NewStringParser(string(nginx_conf.SSL)).Parse()
newConfig, err = parser.NewStringParser(string(nginx_conf.SSL)).Parse()
case dto.CACHE:
newConfig = parser.NewStringParser(string(nginx_conf.Cache)).Parse()
newConfig, err = parser.NewStringParser(string(nginx_conf.Cache)).Parse()
case dto.ProxyCache:
newConfig = parser.NewStringParser(string(nginx_conf.ProxyCache)).Parse()
newConfig, err = parser.NewStringParser(string(nginx_conf.ProxyCache)).Parse()
}
if err != nil {
return nil
}
for _, dir := range newConfig.GetDirectives() {
addParam := dto.NginxParam{
+12 -14
View File
@@ -153,22 +153,20 @@ func (r RecycleBinService) Clear() error {
}
func getClashDir(realPath string) (string, error) {
trimmedPath := strings.Trim(realPath, "/")
parts := strings.Split(trimmedPath, "/")
dir := ""
if len(parts) > 0 {
dir = parts[0]
partitions, err := disk.Partitions(false)
if err != nil {
return "", err
partitions, err := disk.Partitions(false)
if err != nil {
return "", err
}
for _, p := range partitions {
if p.Mountpoint == "/" {
continue
}
for _, p := range partitions {
if p.Mountpoint == dir {
if err = createClashDir(path.Join(p.Mountpoint, ".1panel_clash")); err != nil {
return "", err
}
return dir, nil
if strings.HasPrefix(realPath, p.Mountpoint) {
clashDir := path.Join(p.Mountpoint, ".1panel_clash")
if err = createClashDir(path.Join(p.Mountpoint, ".1panel_clash")); err != nil {
return "", err
}
return clashDir, nil
}
}
return constant.RecycleBinDir, createClashDir(constant.RecycleBinDir)
+14
View File
@@ -42,6 +42,7 @@ type IRuntimeService interface {
GetNodeModules(req request.NodeModuleReq) ([]response.NodeModule, error)
OperateNodeModules(req request.NodeModuleOperateReq) error
SyncForRestart() error
SyncRuntimeStatus() error
}
func NewRuntimeService() IRuntimeService {
@@ -586,3 +587,16 @@ func (r *RuntimeService) SyncForRestart() error {
}
return nil
}
func (r *RuntimeService) SyncRuntimeStatus() error {
runtimes, err := runtimeRepo.List()
if err != nil {
return err
}
for _, runtime := range runtimes {
if runtime.Type == constant.RuntimeNode {
_ = SyncRuntimeContainerStatus(&runtime)
}
}
return nil
}
+12 -25
View File
@@ -181,33 +181,20 @@ func SyncRuntimeContainerStatus(runtime *model.Runtime) error {
}
container := containers[0]
interval := 10 * time.Second
retries := 60
for i := 0; i < retries; i++ {
resp, err := cli.InspectContainer(container.ID)
if err != nil {
time.Sleep(interval)
continue
switch container.State {
case "exited":
runtime.Status = constant.RuntimeError
case "running":
runtime.Status = constant.RuntimeRunning
case "paused":
runtime.Status = constant.RuntimeStopped
default:
if runtime.Status != constant.RuntimeBuildIng {
runtime.Status = constant.RuntimeStopped
}
if resp.State.Health != nil {
status := strings.ToLower(resp.State.Health.Status)
switch status {
case "starting":
runtime.Status = constant.RuntimeStarting
_ = runtimeRepo.Save(runtime)
case "healthy":
runtime.Status = constant.RuntimeRunning
_ = runtimeRepo.Save(runtime)
return nil
case "unhealthy":
runtime.Status = constant.RuntimeUnhealthy
_ = runtimeRepo.Save(runtime)
return nil
}
}
time.Sleep(interval)
}
return nil
return runtimeRepo.Save(runtime)
}
func buildRuntime(runtime *model.Runtime, oldImageID string, rebuild bool) {
+8 -2
View File
@@ -198,6 +198,9 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
}
_ = os.Remove(path.Join(secretDir, "server.crt"))
_ = os.Remove(path.Join(secretDir, "server.key"))
sID, _ := c.Cookie(constant.SessionName)
c.SetCookie(constant.SessionName, sID, 0, "", "", false, true)
go func() {
_, err := cmd.Exec("systemctl restart 1panel.service")
if err != nil {
@@ -277,7 +280,7 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
if err := fileOp.WriteFile(path.Join(secretDir, "server.key.tmp"), strings.NewReader(key), 0600); err != nil {
return err
}
if err := checkCertValid(req.Domain); err != nil {
if err := checkCertValid(); err != nil {
return err
}
if err := fileOp.Rename(path.Join(secretDir, "server.crt.tmp"), path.Join(secretDir, "server.crt")); err != nil {
@@ -289,6 +292,9 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
if err := settingRepo.Update("SSL", req.SSL); err != nil {
return err
}
sID, _ := c.Cookie(constant.SessionName)
c.SetCookie(constant.SessionName, sID, 0, "", "", true, true)
go func() {
time.Sleep(1 * time.Second)
_, err := cmd.Exec("systemctl restart 1panel.service")
@@ -412,7 +418,7 @@ func loadInfoFromCert() (*dto.SSLInfo, error) {
}, nil
}
func checkCertValid(domain string) error {
func checkCertValid() error {
certificate, err := os.ReadFile(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt.tmp"))
if err != nil {
return err
+69 -646
View File
@@ -6,8 +6,6 @@ import (
"fmt"
"os"
"path"
"regexp"
"strconv"
"strings"
"sync"
"time"
@@ -18,10 +16,10 @@ import (
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/compose"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"github.com/shirou/gopsutil/v3/host"
)
type SnapshotService struct {
@@ -74,11 +72,7 @@ func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
for _, snap := range req.Names {
shortName := strings.TrimPrefix(snap, "snapshot_")
nameItems := strings.Split(shortName, "_")
if !strings.HasPrefix(shortName, "1panel_v") || !strings.HasSuffix(shortName, ".tar.gz") || len(nameItems) != 3 {
return fmt.Errorf("incorrect snapshot name format of %s", shortName)
}
formatTime, err := time.Parse("20060102150405", strings.ReplaceAll(nameItems[2], ".tar.gz", ""))
if err != nil {
if !strings.HasPrefix(shortName, "1panel_v") || !strings.HasSuffix(shortName, ".tar.gz") || len(nameItems) < 3 {
return fmt.Errorf("incorrect snapshot name format of %s", shortName)
}
if strings.HasSuffix(snap, ".tar.gz") {
@@ -91,10 +85,6 @@ func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
Version: nameItems[1],
Description: req.Description,
Status: constant.StatusSuccess,
BaseModel: model.BaseModel{
CreatedAt: formatTime,
UpdatedAt: formatTime,
},
}
if err := snapshotRepo.Create(&itemSnap); err != nil {
return err
@@ -145,26 +135,13 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
if err != nil {
return err
}
if hasOs(snap.Name) && !strings.Contains(snap.Name, loadOs()) {
return fmt.Errorf("Restoring snapshots(%s) between different server architectures(%s) is not supported.", snap.Name, loadOs())
}
if !req.IsNew && len(snap.InterruptStep) != 0 && len(snap.RollbackStatus) != 0 {
return fmt.Errorf("the snapshot has been rolled back and cannot be restored again")
}
isNewSnapshot := isNewSnapVersion(snap.Version)
isReTry := false
if len(snap.InterruptStep) != 0 && !req.IsNew {
isReTry = true
}
backup, err := backupRepo.Get(commonRepo.WithByType(snap.DefaultDownload))
if err != nil {
return err
}
client, err := NewIBackupService().NewClient(&backup)
if err != nil {
return err
}
localDir, err := loadLocalDir()
if err != nil {
return err
}
baseDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("system/%s", snap.Name))
if _, err := os.Stat(baseDir); err != nil && os.IsNotExist(err) {
_ = os.MkdirAll(baseDir, os.ModePerm)
@@ -172,151 +149,7 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"recover_status": constant.StatusWaiting})
_ = settingRepo.Update("SystemStatus", "Recovering")
go func() {
_ = global.Cron.Stop()
defer func() {
global.Cron.Start()
}()
operation := "recover"
if isReTry {
operation = "re-recover"
}
if !isReTry || snap.InterruptStep == "Download" || (isReTry && req.ReDownload) {
itemBackupPath := strings.TrimPrefix(backup.BackupPath, "/")
itemBackupPath = strings.TrimSuffix(itemBackupPath, "/")
ok, err := client.Download(fmt.Sprintf("%s/system_snapshot/%s.tar.gz", itemBackupPath, snap.Name), fmt.Sprintf("%s/%s.tar.gz", baseDir, snap.Name))
if err != nil || !ok {
if req.ReDownload {
updateRecoverStatus(snap.ID, snap.InterruptStep, constant.StatusFailed, fmt.Sprintf("download file %s from %s failed, err: %v", snap.Name, backup.Type, err))
return
}
updateRecoverStatus(snap.ID, "Download", constant.StatusFailed, fmt.Sprintf("download file %s from %s failed, err: %v", snap.Name, backup.Type, err))
return
}
isReTry = false
}
fileOp := files.NewFileOp()
if !isReTry || snap.InterruptStep == "Decompress" || (isReTry && req.ReDownload) {
if err := handleUnTar(fmt.Sprintf("%s/%s.tar.gz", baseDir, snap.Name), baseDir); err != nil {
if req.ReDownload {
updateRecoverStatus(snap.ID, snap.InterruptStep, constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
return
}
updateRecoverStatus(snap.ID, "Decompress", constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
return
}
isReTry = false
}
rootDir := fmt.Sprintf("%s/%s", baseDir, snap.Name)
snapJson, err := u.readFromJson(fmt.Sprintf("%s/snapshot.json", rootDir))
if err != nil {
updateRecoverStatus(snap.ID, "Readjson", constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
return
}
if snap.InterruptStep == "Readjson" {
isReTry = false
}
u.OriginalPath = fmt.Sprintf("%s/1panel_original/original_%s", snapJson.BaseDir, snap.Name)
_ = os.MkdirAll(u.OriginalPath, os.ModePerm)
snapJson.OldBaseDir = global.CONF.System.BaseDir
snapJson.OldPanelDataDir = path.Join(global.CONF.System.BaseDir, "1panel")
snapJson.OldBackupDataDir = localDir
recoverPanelDir := fmt.Sprintf("%s/%s/1panel", baseDir, snap.Name)
liveRestore := false
if !isNewSnapshot {
if !isReTry || snap.InterruptStep == "LoadDockerJson" {
snapJson.OldDockerDataDir, liveRestore, err = u.loadDockerDataDir()
if err != nil {
updateRecoverStatus(snap.ID, "LoadDockerJson", constant.StatusFailed, fmt.Sprintf("load docker data dir failed, err: %v", err))
return
}
isReTry = false
}
if liveRestore {
if err := u.updateLiveRestore(false); err != nil {
updateRecoverStatus(snap.ID, "UpdateLiveRestore", constant.StatusFailed, fmt.Sprintf("update docker daemon.json live-restore conf failed, err: %v", err))
return
}
isReTry = false
}
_ = u.saveJson(snapJson, rootDir)
_, _ = cmd.Exec("systemctl stop docker")
if !isReTry || snap.InterruptStep == "DockerDir" {
if err := u.handleDockerDatas(fileOp, operation, rootDir, snapJson.DockerDataDir); err != nil {
updateRecoverStatus(snap.ID, "DockerDir", constant.StatusFailed, err.Error())
return
}
isReTry = false
}
} else {
if !isReTry || snap.InterruptStep == "DockerDir" {
if err := u.handleDockerDatasWithSave(fileOp, operation, rootDir, ""); err != nil {
updateRecoverStatus(snap.ID, "DockerDir", constant.StatusFailed, err.Error())
return
}
isReTry = false
}
}
if !isReTry || snap.InterruptStep == "DaemonJson" {
if err := u.handleDaemonJson(fileOp, operation, rootDir+"/docker/daemon.json", u.OriginalPath); err != nil {
updateRecoverStatus(snap.ID, "DaemonJson", constant.StatusFailed, err.Error())
return
}
isReTry = false
}
_, _ = cmd.Exec("systemctl restart docker")
if !isReTry || snap.InterruptStep == "1PanelBinary" {
if err := u.handlePanelBinary(fileOp, operation, recoverPanelDir+"/1panel", u.OriginalPath+"/1panel"); err != nil {
updateRecoverStatus(snap.ID, "1PanelBinary", constant.StatusFailed, err.Error())
return
}
isReTry = false
}
if !isReTry || snap.InterruptStep == "1PctlBinary" {
if err := u.handlePanelctlBinary(fileOp, operation, recoverPanelDir+"/1pctl", u.OriginalPath+"/1pctl"); err != nil {
updateRecoverStatus(snap.ID, "1PctlBinary", constant.StatusFailed, err.Error())
return
}
isReTry = false
}
if !isReTry || snap.InterruptStep == "1PanelService" {
if err := u.handlePanelService(fileOp, operation, recoverPanelDir+"/1panel.service", u.OriginalPath+"/1panel.service"); err != nil {
updateRecoverStatus(snap.ID, "1PanelService", constant.StatusFailed, err.Error())
return
}
isReTry = false
}
if !isReTry || snap.InterruptStep == "1PanelBackups" {
if err := u.handleBackupDatas(fileOp, operation, rootDir, snapJson.BackupDataDir); err != nil {
updateRecoverStatus(snap.ID, "1PanelBackups", constant.StatusFailed, err.Error())
return
}
isReTry = false
}
if !isReTry || snap.InterruptStep == "1PanelData" {
if err := u.handlePanelDatas(snap.ID, fileOp, operation, rootDir, snapJson.PanelDataDir, localDir, snapJson.OldDockerDataDir); err != nil {
updateRecoverStatus(snap.ID, "1PanelData", constant.StatusFailed, err.Error())
return
}
isReTry = false
}
if isNewSnapshot {
_ = rebuildAllAppInstall()
}
_ = os.RemoveAll(rootDir)
global.LOG.Info("recover successful")
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
}()
go u.HandleSnapshotRecover(snap, true, req)
return nil
}
@@ -326,132 +159,9 @@ func (u *SnapshotService) SnapshotRollback(req dto.SnapshotRecover) error {
if err != nil {
return err
}
if snap.InterruptStep == "Download" || snap.InterruptStep == "Decompress" || snap.InterruptStep == "Readjson" {
return nil
}
localDir, err := loadLocalDir()
if err != nil {
return err
}
fileOp := files.NewFileOp()
isNewSnapshot := isNewSnapVersion(snap.Version)
rootDir := path.Join(localDir, fmt.Sprintf("system/%s/%s", snap.Name, snap.Name))
_ = settingRepo.Update("SystemStatus", "Rollbacking")
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"rollback_status": constant.StatusWaiting})
go func() {
_ = global.Cron.Stop()
defer func() {
global.Cron.Start()
}()
snapJson, err := u.readFromJson(fmt.Sprintf("%s/snapshot.json", rootDir))
if err != nil {
updateRollbackStatus(snap.ID, constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
return
}
u.OriginalPath = fmt.Sprintf("%s/1panel_original/original_%s", snapJson.OldBaseDir, snap.Name)
if _, err := os.Stat(u.OriginalPath); err != nil && os.IsNotExist(err) {
return
}
if !isNewSnapshot {
_, _ = cmd.Exec("systemctl stop docker")
if err := u.handleDockerDatas(fileOp, "rollback", u.OriginalPath, snapJson.OldDockerDataDir); err != nil {
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
defer func() {
_, _ = cmd.Exec("systemctl restart docker")
}()
if snap.InterruptStep == "DockerDir" {
return
}
if snapJson.LiveRestoreEnabled {
if err := u.updateLiveRestore(true); err != nil {
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
}
if snap.InterruptStep == "UpdateLiveRestore" {
return
}
} else {
if err := u.handleDockerDatasWithSave(fileOp, "rollback", u.OriginalPath, ""); err != nil {
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
defer func() {
_ = rebuildAllAppInstall()
}()
if snap.InterruptStep == "DockerDir" {
return
}
}
if err := u.handleDaemonJson(fileOp, "rollback", u.OriginalPath+"/daemon.json", ""); err != nil {
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
if snap.InterruptStep == "DaemonJson" {
return
}
if err := u.handlePanelBinary(fileOp, "rollback", u.OriginalPath+"/1panel", ""); err != nil {
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
if snap.InterruptStep == "1PanelBinary" {
return
}
if err := u.handlePanelctlBinary(fileOp, "rollback", u.OriginalPath+"/1pctl", ""); err != nil {
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
if snap.InterruptStep == "1PctlBinary" {
return
}
if err := u.handlePanelService(fileOp, "rollback", u.OriginalPath+"/1panel.service", ""); err != nil {
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
if snap.InterruptStep == "1PanelService" {
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
return
}
if err := u.handleBackupDatas(fileOp, "rollback", u.OriginalPath, snapJson.OldBackupDataDir); err != nil {
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
if snap.InterruptStep == "1PanelBackups" {
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
return
}
if err := u.handlePanelDatas(snap.ID, fileOp, "rollback", u.OriginalPath, snapJson.OldPanelDataDir, "", ""); err != nil {
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
if snap.InterruptStep == "1PanelData" {
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
return
}
_ = os.RemoveAll(rootDir)
global.LOG.Info("rollback successful")
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
}()
return nil
}
func (u *SnapshotService) saveJson(snapJson SnapshotJson, path string) error {
remarkInfo, _ := json.MarshalIndent(snapJson, "", "\t")
if err := os.WriteFile(fmt.Sprintf("%s/snapshot.json", path), remarkInfo, 0640); err != nil {
return err
}
req.IsNew = false
snap.InterruptStep = "Readjson"
go u.HandleSnapshotRecover(snap, false, req)
return nil
}
@@ -483,9 +193,10 @@ func (u *SnapshotService) HandleSnapshot(isCronjob bool, logPath string, req dto
if req.ID == 0 {
versionItem, _ := settingRepo.Get(settingRepo.WithByKey("SystemVersion"))
name := fmt.Sprintf("1panel_%s_%s", versionItem.Value, timeNow)
name := fmt.Sprintf("1panel_%s_%s_%s", versionItem.Value, loadOs(), timeNow)
if isCronjob {
name = fmt.Sprintf("snapshot_1panel_%s_%s", versionItem.Value, timeNow)
name = fmt.Sprintf("snapshot_1panel_%s_%s_%s", versionItem.Value, loadOs(), timeNow)
}
rootDir = path.Join(localDir, "system", name)
@@ -505,6 +216,7 @@ func (u *SnapshotService) HandleSnapshot(isCronjob bool, logPath string, req dto
if err != nil {
return "", err
}
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusWaiting})
snapStatus, _ = snapshotRepo.GetStatus(snap.ID)
if snapStatus.ID == 0 {
snapStatus.SnapID = snap.ID
@@ -554,17 +266,23 @@ func (u *SnapshotService) HandleSnapshot(isCronjob bool, logPath string, req dto
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
return
}
snapPanelData(itemHelper, localDir, backupPanelDir)
if snapStatus.PanelData != constant.StatusDone {
snapPanelData(itemHelper, localDir, backupPanelDir)
}
if snapStatus.PanelData != constant.StatusDone {
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
return
}
snapCompress(itemHelper, rootDir)
if snapStatus.Compress != constant.StatusDone {
snapCompress(itemHelper, rootDir)
}
if snapStatus.Compress != constant.StatusDone {
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
return
}
snapUpload(itemHelper, req.From, fmt.Sprintf("%s.tar.gz", rootDir))
if snapStatus.Upload != constant.StatusDone {
snapUpload(itemHelper, req.From, fmt.Sprintf("%s.tar.gz", rootDir))
}
if snapStatus.Upload != constant.StatusDone {
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
return
@@ -605,242 +323,6 @@ func (u *SnapshotService) HandleSnapshot(isCronjob bool, logPath string, req dto
return snap.Name, nil
}
func (u *SnapshotService) handleDockerDatas(fileOp files.FileOp, operation string, source, target string) error {
switch operation {
case "snapshot":
if err := handleSnapTar(source, target, "docker_data.tar.gz", ""); err != nil {
return fmt.Errorf("backup docker data failed, err: %v", err)
}
case "recover":
if err := handleSnapTar(target, u.OriginalPath, "docker_data.tar.gz", ""); err != nil {
return fmt.Errorf("backup docker data failed, err: %v", err)
}
if err := u.handleUnTar(source+"/docker/docker_data.tar.gz", target); err != nil {
return fmt.Errorf("recover docker data failed, err: %v", err)
}
case "re-recover":
if err := u.handleUnTar(source+"/docker/docker_data.tar.gz", target); err != nil {
return fmt.Errorf("re-recover docker data failed, err: %v", err)
}
case "rollback":
if err := u.handleUnTar(source+"/docker_data.tar.gz", target); err != nil {
return fmt.Errorf("rollback docker data failed, err: %v", err)
}
}
global.LOG.Info("handle docker data dir successful!")
return nil
}
func (u *SnapshotService) handleDockerDatasWithSave(fileOp files.FileOp, operation, source, target string) error {
switch operation {
case "recover":
if err := u.handleDockerDatasWithSave(fileOp, "snapshot", "", u.OriginalPath); err != nil {
return fmt.Errorf("backup docker data failed, err: %v", err)
}
std, err := cmd.Execf("docker load < %s", path.Join(source, "docker/docker_image.tar"))
if err != nil {
return errors.New(std)
}
case "re-recover":
std, err := cmd.Execf("docker load < %s", path.Join(source, "docker/docker_image.tar"))
if err != nil {
return errors.New(std)
}
case "rollback":
std, err := cmd.Execf("docker load < %s", path.Join(source, "docker_image.tar"))
if err != nil {
return errors.New(std)
}
}
global.LOG.Info("handle docker data successful!")
return nil
}
func (u *SnapshotService) handleDaemonJson(fileOp files.FileOp, operation string, source, target string) error {
daemonJsonPath := "/etc/docker/daemon.json"
if operation == "snapshot" || operation == "recover" {
_, err := os.Stat(daemonJsonPath)
if os.IsNotExist(err) {
global.LOG.Info("no daemon.json in snapshot and system now, nothing happened")
}
if err == nil {
if err := fileOp.CopyFile(daemonJsonPath, target); err != nil {
return fmt.Errorf("backup docker daemon.json failed, err: %v", err)
}
}
}
if operation == "recover" || operation == "rollback" || operation == "re-recover" {
_, sourceErr := os.Stat(source)
if os.IsNotExist(sourceErr) {
_ = os.Remove(daemonJsonPath)
}
if sourceErr == nil {
if err := fileOp.CopyFile(source, "/etc/docker"); err != nil {
return fmt.Errorf("recover docker daemon.json failed, err: %v", err)
}
}
}
global.LOG.Info("handle docker daemon.json successful!")
return nil
}
func (u *SnapshotService) handlePanelBinary(fileOp files.FileOp, operation string, source, target string) error {
panelPath := "/usr/local/bin/1panel"
if operation == "snapshot" || operation == "recover" {
if _, err := os.Stat(panelPath); err != nil {
return fmt.Errorf("1panel binary is not found in %s, err: %v", panelPath, err)
} else {
if err := cpBinary([]string{panelPath}, target); err != nil {
return fmt.Errorf("backup 1panel binary failed, err: %v", err)
}
}
}
if operation == "recover" || operation == "rollback" || operation == "re-recover" {
if _, err := os.Stat(source); err != nil {
return fmt.Errorf("1panel binary is not found in snapshot, err: %v", err)
} else {
if err := cpBinary([]string{source}, "/usr/local/bin/1panel"); err != nil {
return fmt.Errorf("recover 1panel binary failed, err: %v", err)
}
}
}
global.LOG.Info("handle binary panel successful!")
return nil
}
func (u *SnapshotService) handlePanelctlBinary(fileOp files.FileOp, operation string, source, target string) error {
panelctlPath := "/usr/local/bin/1pctl"
if operation == "snapshot" || operation == "recover" {
if _, err := os.Stat(panelctlPath); err != nil {
return fmt.Errorf("1pctl binary is not found in %s, err: %v", panelctlPath, err)
} else {
if err := cpBinary([]string{panelctlPath}, target); err != nil {
return fmt.Errorf("backup 1pctl binary failed, err: %v", err)
}
}
}
if operation == "recover" || operation == "rollback" || operation == "re-recover" {
if _, err := os.Stat(source); err != nil {
return fmt.Errorf("1pctl binary is not found in snapshot, err: %v", err)
} else {
if err := cpBinary([]string{source}, "/usr/local/bin/1pctl"); err != nil {
return fmt.Errorf("recover 1pctl binary failed, err: %v", err)
}
}
}
global.LOG.Info("handle binary 1pactl successful!")
return nil
}
func (u *SnapshotService) handlePanelService(fileOp files.FileOp, operation string, source, target string) error {
panelServicePath := "/etc/systemd/system/1panel.service"
if operation == "snapshot" || operation == "recover" {
if _, err := os.Stat(panelServicePath); err != nil {
return fmt.Errorf("1panel service is not found in %s, err: %v", panelServicePath, err)
} else {
if err := cpBinary([]string{panelServicePath}, target); err != nil {
return fmt.Errorf("backup 1panel service failed, err: %v", err)
}
}
}
if operation == "recover" || operation == "rollback" || operation == "re-recover" {
if _, err := os.Stat(source); err != nil {
return fmt.Errorf("1panel service is not found in snapshot, err: %v", err)
} else {
if err := cpBinary([]string{source}, "/etc/systemd/system/1panel.service"); err != nil {
return fmt.Errorf("recover 1panel service failed, err: %v", err)
}
}
}
global.LOG.Info("handle panel service successful!")
return nil
}
func (u *SnapshotService) handleBackupDatas(fileOp files.FileOp, operation string, source, target string) error {
switch operation {
case "recover":
if err := handleSnapTar(target, u.OriginalPath, "1panel_backup.tar.gz", "./system;"); err != nil {
return fmt.Errorf("restore original local backup dir data failed, err: %v", err)
}
if err := u.handleUnTar(source+"/1panel/1panel_backup.tar.gz", target); err != nil {
return fmt.Errorf("recover local backup dir data failed, err: %v", err)
}
case "re-recover":
if err := u.handleUnTar(source+"/1panel/1panel_backup.tar.gz", target); err != nil {
return fmt.Errorf("retry recover local backup dir data failed, err: %v", err)
}
case "rollback":
if err := u.handleUnTar(source+"/1panel_backup.tar.gz", target); err != nil {
return fmt.Errorf("rollback local backup dir data failed, err: %v", err)
}
}
global.LOG.Info("handle backup data successful!")
return nil
}
func (u *SnapshotService) handlePanelDatas(snapID uint, fileOp files.FileOp, operation string, source, target, backupDir, dockerDir string) error {
switch operation {
case "snapshot":
exclusionRules := "./tmp;./log;./cache;"
if strings.Contains(backupDir, source) {
exclusionRules += ("." + strings.ReplaceAll(backupDir, source, "") + ";")
}
if strings.Contains(dockerDir, source) {
exclusionRules += ("." + strings.ReplaceAll(dockerDir, source, "") + ";")
}
if err := handleSnapTar(source, target, "1panel_data.tar.gz", exclusionRules); err != nil {
return fmt.Errorf("backup panel data failed, err: %v", err)
}
case "recover":
exclusionRules := "./tmp;./log;./cache;./db/1Panel.db-*;"
if strings.Contains(backupDir, target) {
exclusionRules += ("." + strings.ReplaceAll(backupDir, target, "") + ";")
}
if strings.Contains(dockerDir, target) {
exclusionRules += ("." + strings.ReplaceAll(dockerDir, target, "") + ";")
}
_ = snapshotRepo.Update(snapID, map[string]interface{}{"recover_status": ""})
checkPointOfWal()
if err := handleSnapTar(target, u.OriginalPath, "1panel_data.tar.gz", exclusionRules); err != nil {
return fmt.Errorf("restore original panel data failed, err: %v", err)
}
_ = snapshotRepo.Update(snapID, map[string]interface{}{"recover_status": constant.StatusWaiting})
_ = fileOp.Fs.RemoveAll(path.Join(target, "apps"))
checkPointOfWal()
if err := u.handleUnTar(source+"/1panel/1panel_data.tar.gz", target); err != nil {
return fmt.Errorf("recover panel data failed, err: %v", err)
}
case "re-recover":
_ = fileOp.Fs.RemoveAll(path.Join(target, "apps"))
checkPointOfWal()
if err := u.handleUnTar(source+"/1panel/1panel_data.tar.gz", target); err != nil {
return fmt.Errorf("retry recover panel data failed, err: %v", err)
}
case "rollback":
_ = fileOp.Fs.RemoveAll(path.Join(target, "apps"))
checkPointOfWal()
if err := u.handleUnTar(source+"/1panel_data.tar.gz", target); err != nil {
return fmt.Errorf("rollback panel data failed, err: %v", err)
}
}
return nil
}
func (u *SnapshotService) loadDockerDataDir() (string, bool, error) {
client, err := docker.NewDockerClient()
if err != nil {
return "", false, fmt.Errorf("new docker client failed, err: %v", err)
}
info, err := client.Info(context.Background())
if err != nil {
return "", false, fmt.Errorf("load docker info failed, err: %v", err)
}
return info.DockerRootDir, info.LiveRestoreEnabled, nil
}
func (u *SnapshotService) Delete(req dto.BatchDeleteReq) error {
backups, _ := snapshotRepo.GetList(commonRepo.WithIdsIn(req.Ids))
localDir, err := loadLocalDir()
@@ -863,21 +345,22 @@ func (u *SnapshotService) Delete(req dto.BatchDeleteReq) error {
return nil
}
func updateRecoverStatus(id uint, interruptStep, status string, message string) {
if status != constant.StatusSuccess {
global.LOG.Errorf("recover failed, err: %s", message)
func updateRecoverStatus(id uint, isRecover bool, interruptStep, status, message string) {
if isRecover {
if status != constant.StatusSuccess {
global.LOG.Errorf("recover failed, err: %s", message)
}
if err := snapshotRepo.Update(id, map[string]interface{}{
"interrupt_step": interruptStep,
"recover_status": status,
"recover_message": message,
"last_recovered_at": time.Now().Format("2006-01-02 15:04:05"),
}); err != nil {
global.LOG.Errorf("update snap recover status failed, err: %v", err)
}
_ = settingRepo.Update("SystemStatus", "Free")
return
}
if err := snapshotRepo.Update(id, map[string]interface{}{
"interrupt_step": interruptStep,
"recover_status": status,
"recover_message": message,
"last_recovered_at": time.Now().Format("2006-01-02 15:04:05"),
}); err != nil {
global.LOG.Errorf("update snap recover status failed, err: %v", err)
}
_ = settingRepo.Update("SystemStatus", "Free")
}
func updateRollbackStatus(id uint, status string, message string) {
_ = settingRepo.Update("SystemStatus", "Free")
if status == constant.StatusSuccess {
if err := snapshotRepo.Update(id, map[string]interface{}{
@@ -911,53 +394,6 @@ func cpBinary(src []string, dst string) error {
return nil
}
func (u *SnapshotService) updateLiveRestore(enabled bool) error {
if _, err := os.Stat(constant.DaemonJsonPath); err != nil {
return fmt.Errorf("load docker daemon.json conf failed, err: %v", err)
}
file, err := os.ReadFile(constant.DaemonJsonPath)
if err != nil {
return err
}
daemonMap := make(map[string]interface{})
_ = json.Unmarshal(file, &daemonMap)
if !enabled {
delete(daemonMap, "live-restore")
} else {
daemonMap["live-restore"] = enabled
}
newJson, err := json.MarshalIndent(daemonMap, "", "\t")
if err != nil {
return err
}
if err := os.WriteFile(constant.DaemonJsonPath, newJson, 0640); err != nil {
return err
}
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
return errors.New(stdout)
}
ticker := time.NewTicker(3 * time.Second)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
for range ticker.C {
select {
case <-ctx.Done():
return errors.New("the docker service cannot be restarted")
default:
stdout, err := cmd.Exec("systemctl is-active docker")
if string(stdout) == "active\n" && err == nil {
global.LOG.Info("docker restart with new live-restore successful!")
return nil
}
}
}
return nil
}
func (u *SnapshotService) handleUnTar(sourceDir, targetDir string) error {
if _, err := os.Stat(targetDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(targetDir, os.ModePerm); err != nil {
@@ -1010,64 +446,32 @@ func rebuildAllAppInstall() error {
return nil
}
func isNewSnapVersion(version string) bool {
versionItem := "v1.5.0"
if version == versionItem {
return true
}
version1s := strings.Split(version, ".")
version2s := strings.Split(versionItem, ".")
n := min(len(version1s), len(version2s))
re := regexp.MustCompile("[0-9]+")
for i := 0; i < n; i++ {
sVersion1s := re.FindAllString(version1s[i], -1)
sVersion2s := re.FindAllString(version2s[i], -1)
if len(sVersion1s) == 0 {
return false
}
if len(sVersion2s) == 0 {
return false
}
v1num, _ := strconv.Atoi(sVersion1s[0])
v2num, _ := strconv.Atoi(sVersion2s[0])
if v1num == v2num {
continue
} else {
return v1num > v2num
}
}
return true
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func checkIsAllDone(snapID uint) bool {
status, err := snapshotRepo.GetStatus(snapID)
if err != nil {
return false
}
isOK, _ := checkAllDone(status)
return isOK
}
func checkAllDone(status model.SnapshotStatus) (bool, string) {
if status.Panel != constant.StatusDone {
return false
return false, status.Panel
}
if status.PanelInfo != constant.StatusDone {
return false
return false, status.PanelInfo
}
if status.DaemonJson != constant.StatusDone {
return false
return false, status.DaemonJson
}
if status.AppData != constant.StatusDone {
return false
return false, status.AppData
}
if status.BackupData != constant.StatusDone {
return false
return false, status.BackupData
}
return true
return true, ""
}
func loadLogByStatus(status model.SnapshotStatus, logPath string) {
@@ -1084,9 +488,28 @@ func loadLogByStatus(status model.SnapshotStatus, logPath string) {
file, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
global.LOG.Errorf("write snapshot logs failed, err: %v", err)
return
}
defer file.Close()
_, _ = file.Write([]byte(logs))
}
func hasOs(name string) bool {
return strings.Contains(name, "amd64") ||
strings.Contains(name, "arm64") ||
strings.Contains(name, "armv7") ||
strings.Contains(name, "ppc64le") ||
strings.Contains(name, "s390x")
}
func loadOs() string {
hostInfo, _ := host.Info()
switch hostInfo.KernelArch {
case "x86_64":
return "amd64"
case "armv7l":
return "armv7"
default:
return hostInfo.KernelArch
}
}
+35 -21
View File
@@ -97,21 +97,23 @@ func snapAppData(snap snapHelper, targetDir string) {
}
}
}
for _, rumtime := range runtimes {
for _, runtime := range runtimes {
for _, existImage := range existImages {
if rumtime.Image == existImage && !duplicateMap[rumtime.Image] {
imageSaveList = append(imageSaveList, rumtime.Image)
duplicateMap[rumtime.Image] = true
if runtime.Image == existImage && !duplicateMap[runtime.Image] {
imageSaveList = append(imageSaveList, runtime.Image)
duplicateMap[runtime.Image] = true
}
}
}
global.LOG.Debugf("docker save %s | gzip -c > %s", strings.Join(imageSaveList, " "), path.Join(targetDir, "docker_image.tar"))
std, err := cmd.Execf("docker save %s | gzip -c > %s", strings.Join(imageSaveList, " "), path.Join(targetDir, "docker_image.tar"))
if err != nil {
snap.Status.AppData = err.Error()
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"app_data": std})
return
if len(imageSaveList) != 0 {
global.LOG.Debugf("docker save %s | gzip -c > %s", strings.Join(imageSaveList, " "), path.Join(targetDir, "docker_image.tar"))
std, err := cmd.Execf("docker save %s | gzip -c > %s", strings.Join(imageSaveList, " "), path.Join(targetDir, "docker_image.tar"))
if err != nil {
snap.Status.AppData = err.Error()
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"app_data": std})
return
}
}
snap.Status.AppData = constant.StatusDone
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"app_data": constant.StatusDone})
@@ -121,7 +123,7 @@ func snapBackup(snap snapHelper, localDir, targetDir string) {
defer snap.Wg.Done()
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"backup_data": constant.Running})
status := constant.StatusDone
if err := handleSnapTar(localDir, targetDir, "1panel_backup.tar.gz", "./system;"); err != nil {
if err := handleSnapTar(localDir, targetDir, "1panel_backup.tar.gz", "./system;./system_snapshot;"); err != nil {
status = err.Error()
}
snap.Status.BackupData = status
@@ -136,8 +138,17 @@ func snapPanelData(snap snapHelper, localDir, targetDir string) {
if strings.Contains(localDir, dataDir) {
exclusionRules += ("." + strings.ReplaceAll(localDir, dataDir, "") + ";")
}
ignoreVal, _ := settingRepo.Get(settingRepo.WithByKey("SnapshotIgnore"))
rules := strings.Split(ignoreVal.Value, ",")
for _, ignore := range rules {
if len(ignore) == 0 || cmd.CheckIllegal(ignore) {
continue
}
exclusionRules += ("." + strings.ReplaceAll(ignore, dataDir, "") + ";")
}
_ = snapshotRepo.Update(snap.SnapID, map[string]interface{}{"status": "OnSaveData"})
sysIP, _ := settingRepo.Get(settingRepo.WithByKey("SystemIP"))
_ = settingRepo.Update("SystemIP", "")
checkPointOfWal()
if err := handleSnapTar(dataDir, targetDir, "1panel_data.tar.gz", exclusionRules); err != nil {
status = err.Error()
@@ -146,13 +157,10 @@ func snapPanelData(snap snapHelper, localDir, targetDir string) {
snap.Status.PanelData = status
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel_data": status})
_ = settingRepo.Update("SystemIP", sysIP.Value)
}
func snapCompress(snap snapHelper, rootDir string) {
defer func() {
global.LOG.Debugf("remove snapshot file %s", rootDir)
_ = os.RemoveAll(rootDir)
}()
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"compress": constant.StatusRunning})
tmpDir := path.Join(global.CONF.System.TmpDir, "system")
fileName := fmt.Sprintf("%s.tar.gz", path.Base(rootDir))
@@ -173,15 +181,13 @@ func snapCompress(snap snapHelper, rootDir string) {
snap.Status.Compress = constant.StatusDone
snap.Status.Size = size
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"compress": constant.StatusDone, "size": size})
global.LOG.Debugf("remove snapshot file %s", rootDir)
_ = os.RemoveAll(rootDir)
}
func snapUpload(snap snapHelper, accounts string, file string) {
source := path.Join(global.CONF.System.TmpDir, "system", path.Base(file))
defer func() {
global.LOG.Debugf("remove snapshot file %s", source)
_ = os.Remove(source)
}()
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"upload": constant.StatusUploading})
accountMap, err := loadClientMap(accounts)
if err != nil {
@@ -202,6 +208,9 @@ func snapUpload(snap snapHelper, accounts string, file string) {
}
snap.Status.Upload = constant.StatusDone
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"upload": constant.StatusDone})
global.LOG.Debugf("remove snapshot file %s", source)
_ = os.Remove(source)
}
func handleSnapTar(sourceDir, targetDir, name, exclusionRules string) error {
@@ -211,6 +220,7 @@ func handleSnapTar(sourceDir, targetDir, name, exclusionRules string) error {
}
}
exMap := make(map[string]struct{})
exStr := ""
excludes := strings.Split(exclusionRules, ";")
excludes = append(excludes, "*.sock")
@@ -218,8 +228,12 @@ func handleSnapTar(sourceDir, targetDir, name, exclusionRules string) error {
if len(exclude) == 0 {
continue
}
if _, ok := exMap[exclude]; ok {
continue
}
exStr += " --exclude "
exStr += exclude
exMap[exclude] = struct{}{}
}
commands := fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read -zcf %s %s -C %s .", targetDir+"/"+name, exStr, sourceDir)
+263
View File
@@ -0,0 +1,263 @@
package service
import (
"context"
"fmt"
"os"
"path"
"strings"
"sync"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/pkg/errors"
)
func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover bool, req dto.SnapshotRecover) {
_ = global.Cron.Stop()
defer func() {
global.Cron.Start()
}()
snapFileDir := ""
if isRecover {
baseDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("system/%s", snap.Name))
if _, err := os.Stat(baseDir); err != nil && os.IsNotExist(err) {
_ = os.MkdirAll(baseDir, os.ModePerm)
}
if req.IsNew || snap.InterruptStep == "Backup" {
if err := backupBeforeRecover(snap); err != nil {
updateRecoverStatus(snap.ID, isRecover, "Backup", constant.StatusFailed, fmt.Sprintf("handle backup before recover failed, err: %v", err))
return
}
global.LOG.Debug("handle backup before recover successful!")
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "Download" || req.ReDownload {
if err := handleDownloadSnapshot(snap, baseDir); err != nil {
updateRecoverStatus(snap.ID, isRecover, "Backup", constant.StatusFailed, err.Error())
return
}
global.LOG.Debugf("download snapshot file to %s successful!", baseDir)
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "Decompress" {
if err := handleUnTar(fmt.Sprintf("%s/%s.tar.gz", baseDir, snap.Name), baseDir); err != nil {
updateRecoverStatus(snap.ID, isRecover, "Decompress", constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
return
}
global.LOG.Debug("decompress snapshot file successful!", baseDir)
req.IsNew = true
}
snapFileDir = fmt.Sprintf("%s/%s", baseDir, snap.Name)
} else {
snapFileDir = fmt.Sprintf("%s/1panel_original/original_%s", global.CONF.System.BaseDir, snap.Name)
if _, err := os.Stat(snapFileDir); err != nil {
updateRecoverStatus(snap.ID, isRecover, "", constant.StatusFailed, fmt.Sprintf("cannot find the backup file %s, please try to recover again.", snapFileDir))
return
}
}
snapJson, err := u.readFromJson(fmt.Sprintf("%s/snapshot.json", snapFileDir))
if err != nil {
updateRecoverStatus(snap.ID, isRecover, "Readjson", constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
return
}
if snap.InterruptStep == "Readjson" {
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "AppData" {
if err := recoverAppData(snapFileDir); err != nil {
updateRecoverStatus(snap.ID, isRecover, "DockerDir", constant.StatusFailed, fmt.Sprintf("handle recover app data failed, err: %v", err))
return
}
global.LOG.Debug("recover app data from snapshot file successful!")
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "DaemonJson" {
fileOp := files.NewFileOp()
if err := recoverDaemonJson(snapFileDir, fileOp); err != nil {
updateRecoverStatus(snap.ID, isRecover, "DaemonJson", constant.StatusFailed, err.Error())
return
}
global.LOG.Debug("recover daemon.json from snapshot file successful!")
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "1PanelBinary" {
if err := recoverPanel(path.Join(snapFileDir, "1panel/1panel"), "/usr/local/bin/1panel"); err != nil {
updateRecoverStatus(snap.ID, isRecover, "1PanelBinary", constant.StatusFailed, err.Error())
return
}
global.LOG.Debug("recover 1panel binary from snapshot file successful!")
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "1PctlBinary" {
if err := recoverPanel(path.Join(snapFileDir, "1panel/1pctl"), "/usr/local/bin/1pctl"); err != nil {
updateRecoverStatus(snap.ID, isRecover, "1PctlBinary", constant.StatusFailed, err.Error())
return
}
global.LOG.Debug("recover 1pctl from snapshot file successful!")
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "1PanelService" {
if err := recoverPanel(path.Join(snapFileDir, "1panel/1panel.service"), "/etc/systemd/system/1panel.service"); err != nil {
updateRecoverStatus(snap.ID, isRecover, "1PanelService", constant.StatusFailed, err.Error())
return
}
global.LOG.Debug("recover 1panel service from snapshot file successful!")
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "1PanelBackups" {
if err := u.handleUnTar(path.Join(snapFileDir, "/1panel/1panel_backup.tar.gz"), snapJson.BackupDataDir); err != nil {
updateRecoverStatus(snap.ID, isRecover, "1PanelBackups", constant.StatusFailed, err.Error())
return
}
global.LOG.Debug("recover 1panel backups from snapshot file successful!")
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "1PanelData" {
checkPointOfWal()
if err := u.handleUnTar(path.Join(snapFileDir, "/1panel/1panel_data.tar.gz"), path.Join(snapJson.BaseDir, "1panel")); err != nil {
updateRecoverStatus(snap.ID, isRecover, "1PanelData", constant.StatusFailed, err.Error())
return
}
global.LOG.Debug("recover 1panel data from snapshot file successful!")
req.IsNew = true
}
_ = rebuildAllAppInstall()
restartCompose(path.Join(snapJson.BaseDir, "1panel/docker/compose"))
global.LOG.Info("recover successful")
if !isRecover {
oriPath := fmt.Sprintf("%s/1panel_original/original_%s", global.CONF.System.BaseDir, snap.Name)
global.LOG.Debugf("remove the file %s after the operation is successful", oriPath)
_ = os.RemoveAll(oriPath)
} else {
global.LOG.Debugf("remove the file %s after the operation is successful", path.Dir(snapFileDir))
_ = os.RemoveAll(path.Dir(snapFileDir))
}
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
}
func backupBeforeRecover(snap model.Snapshot) error {
baseDir := fmt.Sprintf("%s/1panel_original/original_%s", global.CONF.System.BaseDir, snap.Name)
var wg sync.WaitGroup
var status model.SnapshotStatus
itemHelper := snapHelper{SnapID: 0, Status: &status, Wg: &wg, FileOp: files.NewFileOp(), Ctx: context.Background()}
jsonItem := SnapshotJson{
BaseDir: global.CONF.System.BaseDir,
BackupDataDir: global.CONF.System.Backup,
PanelDataDir: path.Join(global.CONF.System.BaseDir, "1panel"),
}
_ = os.MkdirAll(path.Join(baseDir, "1panel"), os.ModePerm)
_ = os.MkdirAll(path.Join(baseDir, "docker"), os.ModePerm)
wg.Add(5)
itemHelper.Wg = &wg
go snapJson(itemHelper, jsonItem, baseDir)
go snapPanel(itemHelper, path.Join(baseDir, "1panel"))
go snapDaemonJson(itemHelper, path.Join(baseDir, "docker"))
go snapAppData(itemHelper, path.Join(baseDir, "docker"))
go snapBackup(itemHelper, global.CONF.System.Backup, path.Join(baseDir, "1panel"))
wg.Wait()
allDone, msg := checkAllDone(status)
if !allDone {
return errors.New(msg)
}
snapPanelData(itemHelper, global.CONF.System.BaseDir, path.Join(baseDir, "1panel"))
if status.PanelData != constant.StatusDone {
return errors.New(status.PanelData)
}
return nil
}
func handleDownloadSnapshot(snap model.Snapshot, targetDir string) error {
backup, err := backupRepo.Get(commonRepo.WithByType(snap.DefaultDownload))
if err != nil {
return err
}
client, err := NewIBackupService().NewClient(&backup)
if err != nil {
return err
}
pathItem := backup.BackupPath
if backup.BackupPath != "/" {
pathItem = strings.TrimPrefix(backup.BackupPath, "/")
}
filePath := fmt.Sprintf("%s/%s.tar.gz", targetDir, snap.Name)
_ = os.RemoveAll(filePath)
ok, err := client.Download(path.Join(pathItem, fmt.Sprintf("system_snapshot/%s.tar.gz", snap.Name)), filePath)
if err != nil || !ok {
return fmt.Errorf("download file %s from %s failed, err: %v", snap.Name, backup.Type, err)
}
return nil
}
func recoverAppData(src string) error {
if _, err := os.Stat(path.Join(src, "docker/docker_image.tar")); err != nil {
global.LOG.Debug("no such docker images in snapshot")
return nil
}
std, err := cmd.Execf("docker load < %s", path.Join(src, "docker/docker_image.tar"))
if err != nil {
return errors.New(std)
}
return err
}
func recoverDaemonJson(src string, fileOp files.FileOp) error {
daemonJsonPath := "/etc/docker/daemon.json"
_, errSrc := os.Stat(path.Join(src, "docker/daemon.json"))
_, errPath := os.Stat(daemonJsonPath)
if os.IsNotExist(errSrc) && os.IsNotExist(errPath) {
global.LOG.Debug("the daemon.json file does not exist, nothing happens.")
return nil
}
if errSrc == nil {
if err := fileOp.CopyFile(path.Join(src, "docker/daemon.json"), "/etc/docker"); err != nil {
return fmt.Errorf("recover docker daemon.json failed, err: %v", err)
}
}
_, _ = cmd.Exec("systemctl restart docker")
return nil
}
func recoverPanel(src string, dst string) error {
if _, err := os.Stat(src); err != nil {
return fmt.Errorf("file is not found in %s, err: %v", src, err)
}
global.LOG.Debugf(fmt.Sprintf("\\cp -f %s %s", src, dst))
stdout, err := cmd.Exec(fmt.Sprintf("\\cp -f %s %s", src, dst))
if err != nil {
return fmt.Errorf("cp file failed, stdout: %v, err: %v", stdout, err)
}
return nil
}
func restartCompose(composePath string) {
composes, err := composeRepo.ListRecord()
if err != nil {
return
}
for _, compose := range composes {
pathItem := path.Join(composePath, compose.Name, "docker-compose.yml")
if _, err := os.Stat(pathItem); err != nil {
continue
}
upCmd := fmt.Sprintf("docker-compose -f %s up -d", pathItem)
stdout, err := cmd.Exec(upCmd)
if err != nil {
global.LOG.Debugf("%s failed, err: %v", upCmd, stdout)
}
}
global.LOG.Debug("restart all compose successful!")
}
+5 -1
View File
@@ -177,7 +177,11 @@ func (u *SSHService) Update(req dto.SSHUpdate) error {
},
}
if err := NewIFirewallService().UpdatePortRule(ruleItem); err != nil {
global.LOG.Errorf("reset firewall rules %s -> %s failed, err: %v", req.OldValue, req.OldValue, err)
global.LOG.Errorf("reset firewall rules %s -> %s failed, err: %v", req.OldValue, req.NewValue, err)
}
if err = NewIHostService().Update(1, map[string]interface{}{"port": req.NewValue}); err != nil {
global.LOG.Errorf("reset host port %s -> %s failed, err: %v", req.OldValue, req.NewValue, err)
}
}
+24 -20
View File
@@ -7,7 +7,6 @@ import (
"net/http"
"os"
"path"
"runtime"
"strings"
"time"
@@ -133,24 +132,24 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
if err := cpBinary([]string{tmpDir + "/1panel"}, "/usr/local/bin/1panel"); err != nil {
global.LOG.Errorf("upgrade 1panel failed, err: %v", err)
u.handleRollback(fileOp, originalDir, 1)
u.handleRollback(originalDir, 1)
return
}
if err := cpBinary([]string{tmpDir + "/1pctl"}, "/usr/local/bin/1pctl"); err != nil {
global.LOG.Errorf("upgrade 1pctl failed, err: %v", err)
u.handleRollback(fileOp, originalDir, 2)
u.handleRollback(originalDir, 2)
return
}
if _, err := cmd.Execf("sed -i -e 's#BASE_DIR=.*#BASE_DIR=%s#g' /usr/local/bin/1pctl", global.CONF.System.BaseDir); err != nil {
global.LOG.Errorf("upgrade basedir in 1pctl failed, err: %v", err)
u.handleRollback(fileOp, originalDir, 2)
u.handleRollback(originalDir, 2)
return
}
if err := cpBinary([]string{tmpDir + "/1panel.service"}, "/etc/systemd/system/1panel.service"); err != nil {
global.LOG.Errorf("upgrade 1panel.service failed, err: %v", err)
u.handleRollback(fileOp, originalDir, 3)
u.handleRollback(originalDir, 3)
return
}
@@ -181,7 +180,7 @@ func (u *UpgradeService) handleBackup(fileOp files.FileOp, originalDir string) e
return nil
}
func (u *UpgradeService) handleRollback(fileOp files.FileOp, originalDir string, errStep int) {
func (u *UpgradeService) handleRollback(originalDir string, errStep int) {
dbPath := global.CONF.System.DbPath + "/1Panel.db"
_ = settingRepo.Update("SystemStatus", "Free")
if err := cpBinary([]string{originalDir + "/1Panel.db"}, dbPath); err != nil {
@@ -250,19 +249,24 @@ func (u *UpgradeService) loadReleaseNotes(path string) (string, error) {
}
func loadArch() (string, error) {
switch runtime.GOARCH {
case "amd64", "ppc64le", "s390x", "arm64":
return runtime.GOARCH, nil
case "arm":
std, err := cmd.Exec("uname -m")
if err != nil {
return "", fmt.Errorf("std: %s, err: %s", std, err.Error())
}
if std == "armv7l\n" {
return "armv7", nil
}
return "", fmt.Errorf("unsupport such arch: arm-%s", std)
default:
return "", fmt.Errorf("unsupport such arch: %s", runtime.GOARCH)
std, err := cmd.Exec("uname -a")
if err != nil {
return "", fmt.Errorf("std: %s, err: %s", std, err.Error())
}
if strings.Contains(std, "x86_64") {
return "amd64", nil
}
if strings.Contains(std, "arm64") || strings.Contains(std, "aarch64") {
return "arm64", nil
}
if strings.Contains(std, "armv7l") {
return "armv7", nil
}
if strings.Contains(std, "ppc64le") {
return "ppc64le", nil
}
if strings.Contains(std, "s390x") {
return "s390x", nil
}
return "", fmt.Errorf("unsupported such arch: %s", std)
}
+36 -14
View File
@@ -119,7 +119,16 @@ func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []respons
}
opts = append(opts, commonRepo.WithOrderRuleBy(req.OrderBy, req.Order))
if req.Name != "" {
opts = append(opts, websiteRepo.WithDomainLike(req.Name))
domains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithDomainLike(req.Name))
if len(domains) > 0 {
var websiteIds []uint
for _, domain := range domains {
websiteIds = append(websiteIds, domain.WebsiteID)
}
opts = append(opts, websiteRepo.WithIDs(websiteIds))
} else {
opts = append(opts, websiteRepo.WithDomainLike(req.Name))
}
}
if req.WebsiteGroupID != 0 {
opts = append(opts, websiteRepo.WithGroupID(req.WebsiteGroupID))
@@ -820,30 +829,27 @@ func (w WebsiteService) PreInstallCheck(req request.WebsiteInstallCheckReq) ([]r
} else {
checkIds = append(req.InstallIds, appInstall.ID)
}
for _, id := range checkIds {
if err := syncByID(id); err != nil {
return nil, err
}
}
if len(checkIds) > 0 {
installList, _ := appInstallRepo.ListBy(commonRepo.WithIdsIn(checkIds))
for _, install := range installList {
if err = syncAppInstallStatus(&install); err != nil {
return nil, err
}
res = append(res, response.WebsitePreInstallCheck{
Name: install.Name,
Status: install.Status,
Version: install.Version,
AppName: install.App.Name,
})
if install.Status != constant.StatusRunning {
if install.Status != constant.Running {
showErr = true
}
}
}
if showErr {
return res, nil
} else {
return nil, nil
}
return nil, nil
}
func (w WebsiteService) GetWafConfig(req request.WebsiteWafReq) (response.WebsiteWafConfig, error) {
@@ -1511,13 +1517,19 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
switch req.Operate {
case "create":
config = parser.NewStringParser(string(nginx_conf.Proxy)).Parse()
config, err = parser.NewStringParser(string(nginx_conf.Proxy)).Parse()
if err != nil {
return
}
case "edit":
par, err = parser.NewParser(includePath)
if err != nil {
return
}
config = par.Parse()
config, err = par.Parse()
if err != nil {
return
}
oldContent, err = fileOp.GetContent(includePath)
if err != nil {
return
@@ -1533,6 +1545,7 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
_ = fileOp.Rename(backPath, includePath)
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
}
config.FilePath = includePath
directives := config.Directives
location, ok := directives[0].(*components.Location)
@@ -1610,7 +1623,10 @@ func (w WebsiteService) GetProxies(id uint) (res []request.WebsiteProxyConfig, e
return
}
proxyConfig.Content = string(content)
config = parser.NewStringParser(string(content)).Parse()
config, err = parser.NewStringParser(string(content)).Parse()
if err != nil {
return nil, err
}
directives := config.GetDirectives()
location, ok := directives[0].(*components.Location)
@@ -2057,7 +2073,10 @@ func (w WebsiteService) OperateRedirect(req request.NginxRedirectReq) (err error
if err != nil {
return
}
config = oldPar.Parse()
config, err = oldPar.Parse()
if err != nil {
return
}
oldContent, err = fileOp.GetContent(includePath)
if err != nil {
return
@@ -2198,7 +2217,10 @@ func (w WebsiteService) GetRedirect(id uint) (res []response.NginxRedirectConfig
return
}
redirectConfig.Content = string(content)
config = parser.NewStringParser(string(content)).Parse()
config, err = parser.NewStringParser(string(content)).Parse()
if err != nil {
return
}
dirs := config.GetDirectives()
if len(dirs) > 0 {
+1 -1
View File
@@ -1,13 +1,13 @@
package service
import (
"encoding/json"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"gopkg.in/square/go-jose.v2/json"
)
type WebsiteDnsAccountService struct {
+22 -12
View File
@@ -2,13 +2,6 @@ package service
import (
"fmt"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/nginx/components"
"gopkg.in/yaml.v3"
"log"
"os"
"path"
@@ -18,6 +11,14 @@ import (
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/nginx/components"
"gopkg.in/yaml.v3"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/dto"
@@ -114,7 +115,7 @@ func createIndexFile(website *model.Website, runtime *model.Runtime) error {
return nil
}
func createProxyFile(website *model.Website, runtime *model.Runtime) error {
func createProxyFile(website *model.Website) error {
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return err
@@ -132,7 +133,10 @@ func createProxyFile(website *model.Website, runtime *model.Runtime) error {
return err
}
}
config := parser.NewStringParser(string(nginx_conf.Proxy)).Parse()
config, err := parser.NewStringParser(string(nginx_conf.Proxy)).Parse()
if err != nil {
return err
}
config.FilePath = filePath
directives := config.Directives
location, ok := directives[0].(*components.Location)
@@ -188,7 +192,7 @@ func createWebsiteFolder(nginxInstall model.AppInstall, website *model.Website,
}
}
if website.Type == constant.Proxy {
if err := createProxyFile(website, runtime); err != nil {
if err := createProxyFile(website); err != nil {
return err
}
}
@@ -208,7 +212,10 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
nginxFileName := website.Alias + ".conf"
configPath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "conf", "conf.d", nginxFileName)
nginxContent := string(nginx_conf.WebsiteDefault)
config := parser.NewStringParser(nginxContent).Parse()
config, err := parser.NewStringParser(nginxContent).Parse()
if err != nil {
return err
}
servers := config.FindServers()
if len(servers) == 0 {
return errors.New("nginx config is not valid")
@@ -872,7 +879,10 @@ func ChangeHSTSConfig(enable bool, nginxInstall model.AppInstall, website model.
if err != nil {
return err
}
config := par.Parse()
config, err := par.Parse()
if err != nil {
return err
}
config.FilePath = path
directives := config.Directives
location, ok := directives[0].(*components.Location)
+1
View File
@@ -12,6 +12,7 @@ const (
Rebuilding = "Rebuilding"
Syncing = "Syncing"
SyncSuccess = "SyncSuccess"
Paused = "Paused"
SyncErr = "SyncErr"
ContainerPrefix = "1Panel-"
+1
View File
@@ -62,6 +62,7 @@ var (
ErrAppNameExist = "ErrAppNameExist"
ErrFileNotFound = "ErrFileNotFound"
ErrFileParseApp = "ErrFileParseApp"
ErrAppParamKey = "ErrAppParamKey"
)
// website
+3
View File
@@ -5,6 +5,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/init/cache/badger_db"
"github.com/1Panel-dev/1Panel/backend/init/session/psession"
"github.com/go-playground/validator/v10"
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/robfig/cron/v3"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
@@ -23,4 +24,6 @@ var (
Cron *cron.Cron
MonitorCronID cron.EntryID
OneDriveCronID cron.EntryID
I18n *i18n.Localizer
)
+25 -27
View File
@@ -2,9 +2,9 @@ package i18n
import (
"embed"
"github.com/1Panel-dev/1Panel/backend/global"
"strings"
ginI18n "github.com/gin-contrib/i18n"
"github.com/gin-gonic/gin"
"github.com/nicksnyder/go-i18n/v2/i18n"
"golang.org/x/text/language"
@@ -12,13 +12,13 @@ import (
)
func GetMsgWithMap(key string, maps map[string]interface{}) string {
content := ""
var content string
if maps == nil {
content = ginI18n.MustGetMessage(&i18n.LocalizeConfig{
content, _ = global.I18n.Localize(&i18n.LocalizeConfig{
MessageID: key,
})
} else {
content = ginI18n.MustGetMessage(&i18n.LocalizeConfig{
content, _ = global.I18n.Localize(&i18n.LocalizeConfig{
MessageID: key,
TemplateData: maps,
})
@@ -32,13 +32,13 @@ func GetMsgWithMap(key string, maps map[string]interface{}) string {
}
func GetErrMsg(key string, maps map[string]interface{}) string {
content := ""
var content string
if maps == nil {
content = ginI18n.MustGetMessage(&i18n.LocalizeConfig{
content, _ = global.I18n.Localize(&i18n.LocalizeConfig{
MessageID: key,
})
} else {
content = ginI18n.MustGetMessage(&i18n.LocalizeConfig{
content, _ = global.I18n.Localize(&i18n.LocalizeConfig{
MessageID: key,
TemplateData: maps,
})
@@ -47,7 +47,7 @@ func GetErrMsg(key string, maps map[string]interface{}) string {
}
func GetMsgByKey(key string) string {
content := ginI18n.MustGetMessage(&i18n.LocalizeConfig{
content, _ := global.I18n.Localize(&i18n.LocalizeConfig{
MessageID: key,
})
return content
@@ -55,24 +55,22 @@ func GetMsgByKey(key string) string {
//go:embed lang/*
var fs embed.FS
var bundle *i18n.Bundle
func GinI18nLocalize() gin.HandlerFunc {
return ginI18n.Localize(
ginI18n.WithBundle(&ginI18n.BundleCfg{
RootPath: "./lang",
AcceptLanguage: []language.Tag{language.Chinese, language.English, language.TraditionalChinese},
DefaultLanguage: language.Chinese,
FormatBundleFile: "yaml",
UnmarshalFunc: yaml.Unmarshal,
Loader: &ginI18n.EmbedLoader{FS: fs},
}),
ginI18n.WithGetLngHandle(
func(context *gin.Context, defaultLng string) string {
lng := context.GetHeader("Accept-Language")
if lng == "" {
return defaultLng
}
return lng
},
))
func UseI18n() gin.HandlerFunc {
return func(context *gin.Context) {
lang := context.GetHeader("Accept-Language")
if lang == "" {
lang = "zh"
}
global.I18n = i18n.NewLocalizer(bundle, lang)
}
}
func Init() {
bundle = i18n.NewBundle(language.Chinese)
bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal)
_, _ = bundle.LoadMessageFileFS(fs, "lang/zh.yaml")
_, _ = bundle.LoadMessageFileFS(fs, "lang/en.yaml")
_, _ = bundle.LoadMessageFileFS(fs, "lang/zh-Hant.yaml")
}
+8
View File
@@ -59,6 +59,8 @@ ErrVersionTooLow: 'The current 1Panel version is too low to update the app store
ErrAppNameExist: 'App name is already exist'
AppStoreIsSyncing: 'The App Store is syncing, please try again later'
ErrGetCompose: "Failed to obtain docker-compose.yml file! {{ .detail }}"
ErrAppWarn: "Abnormal status, please check the log"
ErrAppParamKey: "Parameter {{ .name }} field exception"
#file
ErrFileCanNotRead: "File can not read"
@@ -164,3 +166,9 @@ CutWebsiteLogSuccess: "{{ .name }} website log cut successfully, backup path {{
#toolbox
ErrNotExistUser: "The current user does not exist. Please modify and retry!"
ErrBanAction: "Setting failed, the current {{ .name }} service is unavailable, please check and try again!"
#waf
ErrScope: "Modification of this configuration is not supported"
ErrStateChange: "State modification failed"
ErrRuleExist: "Rule is Exist"
ErrRuleNotExist: "Rule is not Exist"
+8
View File
@@ -59,6 +59,8 @@ ErrVersionTooLow: '當前 1Panel 版本過低,無法更新應用商店,請
ErrAppNameExist: '應用名稱已存在'
AppStoreIsSyncing: '應用程式商店正在同步中,請稍後再試'
ErrGetCompose: "docker-compose.yml 檔案取得失敗!{{ .detail }}"
ErrAppWarn: "狀態異常,請查看日誌"
ErrAppParamKey: "參數 {{ .name }} 欄位異常"
#file
ErrFileCanNotRead: "此文件不支持預覽"
@@ -165,3 +167,9 @@ CutWebsiteLogSuccess: "{{ .name }} 網站日誌切割成功,備份路徑 {{ .p
#toolbox
ErrNotExistUser: "當前使用者不存在,請修改後重試!"
ErrBanAction: "設置失敗,當前 {{ .name }} 服務不可用,請檢查後重試!"
#waf
ErrScope: "不支援修改此配置"
ErrStateChange: "狀態修改失敗"
ErrRuleExist: "規則名稱已存在"
ErrRuleNotExist: "規則不存在"
+9 -1
View File
@@ -59,6 +59,8 @@ ErrVersionTooLow: '当前 1Panel 版本过低,无法更新应用商店,请
ErrAppNameExist: '应用名称已存在'
AppStoreIsSyncing: '应用商店正在同步中,请稍后再试'
ErrGetCompose: "docker-compose.yml 文件获取失败!{{ .detail }}"
ErrAppWarn: "状态异常,请查看日志"
ErrAppParamKey: "参数 {{ .name }} 字段异常"
#file
ErrFileCanNotRead: "此文件不支持预览"
@@ -163,4 +165,10 @@ CutWebsiteLogSuccess: "{{ .name }} 网站日志切割成功,备份路径 {{ .p
#toolbox
ErrNotExistUser: "当前用户不存在,请修改后重试!"
ErrBanAction: "设置失败,当前 {{ .name }} 服务不可用,请检查后重试!"
ErrBanAction: "设置失败,当前 {{ .name }} 服务不可用,请检查后重试!"
#waf
ErrScope: "不支持修改此配置"
ErrStateChange: "状态修改失败"
ErrRuleExist: "规则名称已存在"
ErrRuleNotExist: "规则不存在"
+1 -1
View File
@@ -23,7 +23,7 @@ func syncApp() {
func syncInstalledApp() {
if err := service.NewIAppInstalledService().SyncAll(true); err != nil {
global.LOG.Errorf("sync instaled app error: %s", err.Error())
global.LOG.Errorf("sync installed app error: %s", err.Error())
}
}
-13
View File
@@ -5,7 +5,6 @@ import (
"time"
"github.com/dgraph-io/badger/v4"
"github.com/pkg/errors"
)
type Cache struct {
@@ -18,18 +17,6 @@ func NewCacheDB(db *badger.DB) *Cache {
}
}
func (c *Cache) SetNX(key string, value interface{}) error {
err := c.db.Update(func(txn *badger.Txn) error {
_, err := txn.Get([]byte(key))
if errors.Is(err, badger.ErrKeyNotFound) {
v := []byte(fmt.Sprintf("%v", value))
return txn.Set([]byte(key), v)
}
return err
})
return err
}
func (c *Cache) Set(key string, value interface{}) error {
err := c.db.Update(func(txn *badger.Txn) error {
v := []byte(fmt.Sprintf("%v", value))
+4 -6
View File
@@ -6,24 +6,22 @@ import (
"os"
"time"
"gorm.io/gorm/logger"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
"github.com/1Panel-dev/1Panel/backend/global"
"gorm.io/gorm/logger"
)
func Init() {
if _, err := os.Stat(global.CONF.System.DbPath); err != nil {
if err := os.MkdirAll(global.CONF.System.DbPath, os.ModePerm); err != nil {
panic(fmt.Errorf("init db dir falied, err: %v", err))
panic(fmt.Errorf("init db dir failed, err: %v", err))
}
}
fullPath := global.CONF.System.DbPath + "/" + global.CONF.System.DbFile
if _, err := os.Stat(fullPath); err != nil {
if _, err := os.Create(fullPath); err != nil {
panic(fmt.Errorf("init db file falied, err: %v", err))
panic(fmt.Errorf("init db file failed, err: %v", err))
}
}
+2 -2
View File
@@ -56,8 +56,8 @@ type MineFormatter struct{}
func (s *MineFormatter) Format(entry *logrus.Entry) ([]byte, error) {
detailInfo := ""
if entry.Caller != nil {
funcion := strings.ReplaceAll(entry.Caller.Function, "github.com/1Panel-dev/1Panel/backend/", "")
detailInfo = fmt.Sprintf("(%s: %d)", funcion, entry.Caller.Line)
function := strings.ReplaceAll(entry.Caller.Function, "github.com/1Panel-dev/1Panel/backend/", "")
detailInfo = fmt.Sprintf("(%s: %d)", function, entry.Caller.Line)
}
if len(entry.Data) == 0 {
msg := fmt.Sprintf("[%s] [%s] %s %s \n", time.Now().Format(TimeFormat), strings.ToUpper(entry.Level.String()), entry.Message, detailInfo)
+5
View File
@@ -70,6 +70,11 @@ func Init() {
migrations.UpdateCronjobSpec,
migrations.UpdateBackupRecordPath,
migrations.UpdateSnapshotRecords,
migrations.UpdateWebDavConf,
migrations.AddSnapshotIgnore,
migrations.AddDatabaseIsDelete,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
@@ -0,0 +1,27 @@
package migrations
import (
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
var AddSnapshotIgnore = &gormigrate.Migration{
ID: "20240311-add-snapshot-ignore",
Migrate: func(tx *gorm.DB) error {
if err := tx.Create(&model.Setting{Key: "SnapshotIgnore", Value: "*.sock"}).Error; err != nil {
return err
}
return nil
},
}
var AddDatabaseIsDelete = &gormigrate.Migration{
ID: "20240314-add-database-is-delete",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.DatabaseMysql{}, &model.DatabasePostgresql{}); err != nil {
return err
}
return nil
},
}
+44 -3
View File
@@ -124,6 +124,12 @@ var AddTablePHPExtensions = &gormigrate.Migration{
if err := tx.Create(&model.PHPExtensions{Name: "默认", Extensions: "bcmath,gd,gettext,intl,pcntl,shmop,soap,sockets,sysvsem,xmlrpc,zip"}).Error; err != nil {
return err
}
if err := tx.Create(&model.PHPExtensions{Name: "苹果CMS-V10", Extensions: "mysqli,pdo_mysql,zip,gd,redis,memcache,memcached"}).Error; err != nil {
return err
}
if err := tx.Create(&model.PHPExtensions{Name: "SeaCMS", Extensions: "mysqli,pdo_mysql,gd,curl"}).Error; err != nil {
return err
}
return nil
},
}
@@ -316,7 +322,7 @@ var UpdateCronjobSpec = &gormigrate.Migration{
var records []model.JobRecords
_ = tx.Where("cronjob_id = ?", job.ID).Find(&records).Error
for _, record := range records {
if job.Type == "snapshot" {
if job.Type == "snapshot" && record.Status == constant.StatusSuccess {
var snaps []model.Snapshot
_ = tx.Where("name like ?", "snapshot_"+"%").Find(&snaps).Error
for _, snap := range snaps {
@@ -329,12 +335,15 @@ var UpdateCronjobSpec = &gormigrate.Migration{
FileName: snap.Name + ".tar.gz",
Source: snap.From,
BackupType: snap.From,
BaseModel: model.BaseModel{
CreatedAt: record.CreatedAt,
},
}
_ = tx.Create(&item).Error
}
continue
}
if job.Type == "log" {
if job.Type == "log" && record.Status == constant.StatusSuccess {
item := model.BackupRecord{
From: "cronjob",
CronjobID: job.ID,
@@ -344,11 +353,14 @@ var UpdateCronjobSpec = &gormigrate.Migration{
FileName: path.Base(record.File),
Source: mapAccount[uint(job.TargetDirID)].Type,
BackupType: mapAccount[uint(job.TargetDirID)].Type,
BaseModel: model.BaseModel{
CreatedAt: record.CreatedAt,
},
}
_ = tx.Create(&item).Error
continue
}
if job.Type == "directory" {
if job.Type == "directory" && record.Status == constant.StatusSuccess {
item := model.BackupRecord{
From: "cronjob",
CronjobID: job.ID,
@@ -357,6 +369,9 @@ var UpdateCronjobSpec = &gormigrate.Migration{
FileDir: path.Dir(strings.TrimPrefix(record.File, itemPath)),
FileName: path.Base(record.File),
BackupType: mapAccount[uint(job.TargetDirID)].Type,
BaseModel: model.BaseModel{
CreatedAt: record.CreatedAt,
},
}
if record.FromLocal {
item.Source = constant.Local
@@ -442,3 +457,29 @@ var UpdateSnapshotRecords = &gormigrate.Migration{
return nil
},
}
var UpdateWebDavConf = &gormigrate.Migration{
ID: "20240205-update-webdav-conf",
Migrate: func(tx *gorm.DB) error {
var backup model.BackupAccount
_ = tx.Where("type = ?", constant.WebDAV).First(&backup).Error
if backup.ID == 0 {
return nil
}
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
return err
}
delete(varMap, "addressItem")
if port, ok := varMap["port"]; ok {
varMap["address"] = fmt.Sprintf("%s:%v", varMap["address"], port)
delete(varMap, "port")
}
vars, _ := json.Marshal(varMap)
if err := tx.Model(&model.BackupAccount{}).Where("id = ?", backup.ID).Updates(map[string]interface{}{"vars": string(vars)}).Error; err != nil {
return err
}
return nil
},
}
+1 -6
View File
@@ -9,11 +9,9 @@ import (
"github.com/1Panel-dev/1Panel/cmd/server/docs"
"github.com/1Panel-dev/1Panel/cmd/server/web"
"github.com/gin-contrib/gzip"
ginI18n "github.com/gin-contrib/i18n"
"github.com/gin-gonic/gin"
swaggerfiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
"html/template"
"net/http"
)
@@ -53,10 +51,7 @@ func Routers() *gin.Engine {
c.Writer.Flush()
})
Router.Use(i18n.GinI18nLocalize())
Router.SetFuncMap(template.FuncMap{
"Localize": ginI18n.GetMessage,
})
Router.Use(i18n.UseI18n())
swaggerRouter := Router.Group("1panel")
docs.SwaggerInfo.BasePath = "/api/v1"
+1
View File
@@ -16,6 +16,7 @@ func (s *DashboardRouter) InitRouter(Router *gin.RouterGroup) {
Use(middleware.PasswordExpired())
baseApi := v1.ApiGroupApp.BaseApi
{
cmdRouter.GET("/base/os", baseApi.LoadDashboardOsInfo)
cmdRouter.GET("/base/:ioOption/:netOption", baseApi.LoadDashboardBaseInfo)
cmdRouter.GET("/current/:ioOption/:netOption", baseApi.LoadDashboardCurrentInfo)
cmdRouter.POST("/system/restart/:operation", baseApi.SystemRestart)
+1
View File
@@ -20,6 +20,7 @@ func (r *RuntimeRouter) InitRouter(Router *gin.RouterGroup) {
groupRouter.POST("/del", baseApi.DeleteRuntime)
groupRouter.POST("/update", baseApi.UpdateRuntime)
groupRouter.GET("/:id", baseApi.GetRuntime)
groupRouter.POST("/sync", baseApi.SyncStatus)
groupRouter.POST("/node/package", baseApi.GetNodePackageRunScript)
groupRouter.POST("/operate", baseApi.OperateRuntime)
+2
View File
@@ -4,6 +4,7 @@ import (
"crypto/tls"
"encoding/gob"
"fmt"
"github.com/1Panel-dev/1Panel/backend/i18n"
"net"
"net/http"
"os"
@@ -31,6 +32,7 @@ import (
func Start() {
viper.Init()
i18n.Init()
log.Init()
db.Init()
migration.Init()
+1 -1
View File
@@ -8,7 +8,7 @@ import (
func loadParamFromVars(key string, vars map[string]interface{}) string {
if _, ok := vars[key]; !ok {
if key != "bucket" {
if key != "bucket" && key != "port" {
global.LOG.Errorf("load param %s from vars failed, err: not exist!", key)
}
return ""
+18 -8
View File
@@ -4,6 +4,7 @@ import (
"bufio"
"bytes"
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
@@ -14,6 +15,7 @@ import (
"path"
"strconv"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/utils/files"
odsdk "github.com/goh-chunlin/go-onedrive/onedrive"
@@ -116,7 +118,7 @@ func (o oneDriveClient) Upload(src, target string) (bool, error) {
}
var isOk bool
if fileInfo.Size() < 4*1024*1024 {
isOk, err = o.upSmall(ctx, src, folderID, fileInfo.Size())
isOk, err = o.upSmall(src, folderID, fileInfo.Size())
} else {
isOk, err = o.upBig(ctx, src, folderID, fileInfo.Size())
}
@@ -300,7 +302,7 @@ func (o *oneDriveClient) NewSessionFileUploadRequest(absoluteUrl string, grandOf
return req, err
}
func (o *oneDriveClient) upSmall(ctx context.Context, srcPath, folderID string, fileSize int64) (bool, error) {
func (o *oneDriveClient) upSmall(srcPath, folderID string, fileSize int64) (bool, error) {
file, err := os.Open(srcPath)
if err != nil {
return false, err
@@ -321,7 +323,6 @@ func (o *oneDriveClient) upSmall(ctx context.Context, srcPath, folderID string,
if err := o.client.Do(context.Background(), req, false, &response); err != nil {
return false, fmt.Errorf("do request for list failed, err: %v", err)
}
fmt.Println(response)
return true, nil
}
@@ -362,7 +363,13 @@ func (o *oneDriveClient) upBig(ctx context.Context, srcPath, folderID string, fi
splitCount += 1
}
bfReader := bufio.NewReader(file)
var fileUploadResp *UploadSessionUploadResponse
httpClient := http.Client{
Timeout: time.Minute * 10,
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
for splitNow := int64(0); splitNow < splitCount; splitNow++ {
length, err := bfReader.Read(buffer)
if err != nil {
@@ -376,12 +383,15 @@ func (o *oneDriveClient) upBig(ctx context.Context, srcPath, folderID string, fi
if err != nil {
return false, err
}
if err := o.client.Do(ctx, sessionFileUploadReq, false, &fileUploadResp); err != nil {
res, err := httpClient.Do(sessionFileUploadReq)
if err != nil {
return false, err
}
}
if fileUploadResp.Id == "" {
return false, errors.New("something went wrong. file upload incomplete. consider upload the file in a step-by-step manner")
if res.StatusCode != 201 && res.StatusCode != 202 && res.StatusCode != 200 {
data, _ := io.ReadAll(res.Body)
res.Body.Close()
return false, errors.New(string(data))
}
}
return true, nil
}
@@ -8,6 +8,7 @@ import (
"path"
"time"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
@@ -21,6 +22,9 @@ type sftpClient struct {
func NewSftpClient(vars map[string]interface{}) (*sftpClient, error) {
address := loadParamFromVars("address", vars)
port := loadParamFromVars("port", vars)
if len(port) == 0 {
global.LOG.Errorf("load param port from vars failed, err: not exist!")
}
password := loadParamFromVars("password", vars)
username := loadParamFromVars("username", vars)
bucket := loadParamFromVars("bucket", vars)
@@ -34,6 +38,9 @@ func NewSftpClient(vars map[string]interface{}) (*sftpClient, error) {
return nil
},
}
if _, err := ssh.Dial("tcp", fmt.Sprintf("%s:%s", address, port), clientConfig); err != nil {
return nil, err
}
return &sftpClient{bucket: bucket, connInfo: fmt.Sprintf("%s:%s", address, port), config: clientConfig}, nil
}
+10 -9
View File
@@ -6,6 +6,7 @@ import (
"io"
"net/http"
"os"
"path"
"strings"
"github.com/studio-b12/gowebdav"
@@ -43,7 +44,7 @@ func NewWebDAVClient(vars map[string]interface{}) (*webDAVClient, error) {
}
func (s webDAVClient) Upload(src, target string) (bool, error) {
targetFilePath := s.Bucket + "/" + target
targetFilePath := path.Join(s.Bucket, target)
srcFile, err := os.Open(src)
if err != nil {
return false, err
@@ -62,7 +63,7 @@ func (s webDAVClient) ListBuckets() ([]interface{}, error) {
}
func (s webDAVClient) Download(src, target string) (bool, error) {
srcPath := s.Bucket + "/" + src
srcPath := path.Join(s.Bucket, src)
info, err := s.client.Stat(srcPath)
if err != nil {
return false, err
@@ -85,30 +86,30 @@ func (s webDAVClient) Download(src, target string) (bool, error) {
return true, err
}
func (s webDAVClient) Exist(path string) (bool, error) {
if _, err := s.client.Stat(s.Bucket + "/" + path); err != nil {
func (s webDAVClient) Exist(pathItem string) (bool, error) {
if _, err := s.client.Stat(path.Join(s.Bucket, pathItem)); err != nil {
return false, err
}
return true, nil
}
func (s webDAVClient) Size(path string) (int64, error) {
file, err := s.client.Stat(s.Bucket + "/" + path)
func (s webDAVClient) Size(pathItem string) (int64, error) {
file, err := s.client.Stat(path.Join(s.Bucket, pathItem))
if err != nil {
return 0, err
}
return file.Size(), nil
}
func (s webDAVClient) Delete(filePath string) (bool, error) {
if err := s.client.Remove(s.Bucket + "/" + filePath); err != nil {
func (s webDAVClient) Delete(pathItem string) (bool, error) {
if err := s.client.Remove(path.Join(s.Bucket, pathItem)); err != nil {
return false, err
}
return true, nil
}
func (s webDAVClient) ListObjects(prefix string) ([]string, error) {
files, err := s.client.ReadDir(s.Bucket + "/" + prefix)
files, err := s.client.ReadDir(path.Join(s.Bucket, prefix))
if err != nil {
return nil, err
}
+53 -40
View File
@@ -2,7 +2,6 @@ package cmd
import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
@@ -14,20 +13,7 @@ import (
)
func Exec(cmdStr string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
cmd := exec.Command("bash", "-c", cmdStr)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if ctx.Err() == context.DeadlineExceeded {
return "", buserr.New(constant.ErrCmdTimeout)
}
if err != nil {
return handleErr(stdout, stderr, err)
}
return stdout.String(), nil
return ExecWithTimeOut(cmdStr, 20*time.Second)
}
func handleErr(stdout, stderr bytes.Buffer, err error) (string, error) {
@@ -46,19 +32,28 @@ func handleErr(stdout, stderr bytes.Buffer, err error) (string, error) {
}
func ExecWithTimeOut(cmdStr string, timeout time.Duration) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
cmd := exec.Command("bash", "-c", cmdStr)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if ctx.Err() == context.DeadlineExceeded {
if err := cmd.Start(); err != nil {
return "", err
}
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()
after := time.After(timeout)
select {
case <-after:
_ = cmd.Process.Kill()
return "", buserr.New(constant.ErrCmdTimeout)
case err := <-done:
if err != nil {
return handleErr(stdout, stderr, err)
}
}
if err != nil {
return handleErr(stdout, stderr, err)
}
return stdout.String(), nil
}
@@ -75,9 +70,6 @@ func ExecContainerScript(containerName, cmdStr string, timeout time.Duration) er
}
func ExecCronjobWithTimeOut(cmdStr, workdir, outPath string, timeout time.Duration) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
file, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
return err
@@ -88,13 +80,24 @@ func ExecCronjobWithTimeOut(cmdStr, workdir, outPath string, timeout time.Durati
cmd.Dir = workdir
cmd.Stdout = file
cmd.Stderr = file
err = cmd.Run()
if ctx.Err() == context.DeadlineExceeded {
return buserr.New(constant.ErrCmdTimeout)
if err := cmd.Start(); err != nil {
return err
}
return err
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()
after := time.After(timeout)
select {
case <-after:
_ = cmd.Process.Kill()
return buserr.New(constant.ErrCmdTimeout)
case err := <-done:
if err != nil {
return err
}
}
return nil
}
func Execf(cmdStr string, a ...interface{}) (string, error) {
@@ -122,20 +125,29 @@ func ExecWithCheck(name string, a ...string) (string, error) {
}
func ExecScript(scriptPath, workDir string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
cmd := exec.Command("bash", scriptPath)
cmd.Dir = workDir
var stdout, stderr bytes.Buffer
cmd.Dir = workDir
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if ctx.Err() == context.DeadlineExceeded {
if err := cmd.Start(); err != nil {
return "", err
}
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()
after := time.After(10 * time.Minute)
select {
case <-after:
_ = cmd.Process.Kill()
return "", buserr.New(constant.ErrCmdTimeout)
case err := <-done:
if err != nil {
return handleErr(stdout, stderr, err)
}
}
if err != nil {
return handleErr(stdout, stderr, err)
}
return stdout.String(), nil
}
@@ -165,7 +177,8 @@ func CheckIllegal(args ...string) bool {
for _, arg := range args {
if strings.Contains(arg, "&") || strings.Contains(arg, "|") || strings.Contains(arg, ";") ||
strings.Contains(arg, "$") || strings.Contains(arg, "'") || strings.Contains(arg, "`") ||
strings.Contains(arg, "(") || strings.Contains(arg, ")") || strings.Contains(arg, "\"") {
strings.Contains(arg, "(") || strings.Contains(arg, ")") || strings.Contains(arg, "\"") ||
strings.Contains(arg, "\n") || strings.Contains(arg, "\r") {
return true
}
}
+2 -10
View File
@@ -3,7 +3,6 @@ package common
import (
"crypto/rand"
"fmt"
"golang.org/x/net/idna"
"io"
mathRand "math/rand"
"net"
@@ -15,6 +14,8 @@ import (
"time"
"unicode"
"golang.org/x/net/idna"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
)
@@ -143,15 +144,6 @@ func ScanPortWithProto(port int, proto string) bool {
return ScanPort(port)
}
func ExistWithStrArray(str string, arr []string) bool {
for _, a := range arr {
if strings.Contains(a, str) {
return true
}
}
return false
}
func IsNum(s string) bool {
_, err := strconv.ParseFloat(s, 64)
return err == nil
+29 -37
View File
@@ -2,14 +2,19 @@ package docker
import (
"context"
"github.com/compose-spec/compose-go/loader"
"github.com/compose-spec/compose-go/types"
"github.com/docker/compose/v2/pkg/api"
"github.com/joho/godotenv"
"path"
"regexp"
"strings"
"time"
"github.com/compose-spec/compose-go/v2/loader"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/flags"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/compose"
"github.com/docker/docker/client"
"github.com/joho/godotenv"
)
type ComposeService struct {
@@ -17,8 +22,7 @@ type ComposeService struct {
project *types.Project
}
func (s *ComposeService) SetProject(project *types.Project) {
s.project = project
func UpComposeProject(project *types.Project) error {
for i, s := range project.Services {
s.CustomLabels = map[string]string{
api.ProjectLabel: project.Name,
@@ -30,47 +34,35 @@ func (s *ComposeService) SetProject(project *types.Project) {
}
project.Services[i] = s
}
}
func (s *ComposeService) ComposeUp() error {
return s.Up(context.Background(), s.project, api.UpOptions{
apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
return err
}
var ops []command.CLIOption
ops = append(ops, command.WithAPIClient(apiClient), command.WithDefaultContextStoreConfig())
cli, err := command.NewDockerCli(ops...)
if err != nil {
return err
}
cliOp := flags.NewClientOptions()
if err = cli.Initialize(cliOp); err != nil {
return err
}
service := compose.NewComposeService(cli)
composeService := ComposeService{Service: service}
return composeService.Up(context.Background(), project, api.UpOptions{
Create: api.CreateOptions{
Timeout: getComposeTimeout(),
},
Start: api.StartOptions{
WaitTimeout: *getComposeTimeout(),
Wait: true,
},
})
}
func (s *ComposeService) ComposeDown() error {
return s.Down(context.Background(), s.project.Name, api.DownOptions{})
}
func (s *ComposeService) ComposeStart() error {
return s.Start(context.Background(), s.project.Name, api.StartOptions{})
}
func (s *ComposeService) ComposeRestart() error {
return s.Restart(context.Background(), s.project.Name, api.RestartOptions{})
}
func (s *ComposeService) ComposeStop() error {
return s.Stop(context.Background(), s.project.Name, api.StopOptions{})
}
func (s *ComposeService) ComposeCreate() error {
return s.Create(context.Background(), s.project, api.CreateOptions{})
}
func (s *ComposeService) ComposeBuild() error {
return s.Build(context.Background(), s.project, api.BuildOptions{})
}
func (s *ComposeService) ComposePull() error {
return s.Pull(context.Background(), s.project, api.PullOptions{})
}
func GetComposeProject(projectName, workDir string, yml []byte, env []byte, skipNormalization bool) (*types.Project, error) {
var configFiles []types.ConfigFile
configFiles = append(configFiles, types.ConfigFile{
+9 -14
View File
@@ -3,6 +3,8 @@ package docker
import (
"context"
"github.com/docker/docker/api/types/container"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/global"
@@ -44,24 +46,17 @@ func NewDockerClient() (*client.Client, error) {
return cli, nil
}
func (c Client) ListAllContainers() ([]types.Container, error) {
var options types.ContainerListOptions
containers, err := c.cli.ContainerList(context.Background(), options)
if err != nil {
return nil, err
}
return containers, nil
}
func (c Client) ListContainersByName(names []string) ([]types.Container, error) {
var (
options types.ContainerListOptions
res []types.Container
options container.ListOptions
namesMap = make(map[string]bool)
res []types.Container
)
options.All = true
if len(names) > 0 {
var array []filters.KeyValuePair
for _, n := range names {
namesMap["/"+n] = true
array = append(array, filters.Arg("name", n))
}
options.Filters = filters.NewArgs(array...)
@@ -70,9 +65,9 @@ func (c Client) ListContainersByName(names []string) ([]types.Container, error)
if err != nil {
return nil, err
}
for _, container := range containers {
if container.Names[0] == "/"+names[0] {
res = append(res, container)
for _, con := range containers {
if _, ok := namesMap[con.Names[0]]; ok {
res = append(res, con)
}
}
return res, nil
-8
View File
@@ -4,10 +4,8 @@ import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
@@ -61,12 +59,6 @@ func StringDecrypt(text string) (string, error) {
return "", err
}
func Md5(str string) string {
h := md5.New()
h.Write([]byte(str))
return hex.EncodeToString(h.Sum(nil))
}
func padding(plaintext []byte, blockSize int) []byte {
padding := blockSize - len(plaintext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
+19 -49
View File
@@ -7,11 +7,6 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
http2 "github.com/1Panel-dev/1Panel/backend/utils/http"
cZip "github.com/klauspost/compress/zip"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
"io"
"io/fs"
"net/http"
@@ -23,6 +18,12 @@ import (
"sync"
"time"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
http2 "github.com/1Panel-dev/1Panel/backend/utils/http"
cZip "github.com/klauspost/compress/zip"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/mholt/archiver/v4"
"github.com/pkg/errors"
@@ -85,10 +86,6 @@ func (f FileOp) DeleteFile(dst string) error {
return f.Fs.Remove(dst)
}
func (f FileOp) Delete(dst string) error {
return os.RemoveAll(dst)
}
func (f FileOp) CleanDir(dst string) error {
return cmd.ExecCmd(fmt.Sprintf("rm -rf %s/*", dst))
}
@@ -129,12 +126,19 @@ func (f FileOp) SaveFile(dst string, content string, mode fs.FileMode) error {
return nil
}
func (f FileOp) Chmod(dst string, mode fs.FileMode) error {
return f.Fs.Chmod(dst, mode)
}
func (f FileOp) Chown(dst string, uid int, gid int) error {
return f.Fs.Chown(dst, uid, gid)
func (f FileOp) SaveFileWithByte(dst string, content []byte, mode fs.FileMode) error {
if !f.Stat(path.Dir(dst)) {
_ = f.CreateDir(path.Dir(dst), mode.Perm())
}
file, err := f.Fs.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
if err != nil {
return err
}
defer file.Close()
write := bufio.NewWriter(file)
_, _ = write.Write(content)
write.Flush()
return nil
}
func (f FileOp) ChownR(dst string, uid string, gid string, sub bool) error {
@@ -563,40 +567,6 @@ func (f FileOp) Decompress(srcFile string, dst string, cType CompressType) error
return nil
}
func (f FileOp) Backup(srcFile string) (string, error) {
backupPath := srcFile + "_bak"
info, _ := f.Fs.Stat(backupPath)
if info != nil {
if info.IsDir() {
_ = f.DeleteDir(backupPath)
} else {
_ = f.DeleteFile(backupPath)
}
}
if err := f.Rename(srcFile, backupPath); err != nil {
return backupPath, err
}
return backupPath, nil
}
func (f FileOp) CopyAndBackup(src string) (string, error) {
backupPath := src + "_bak"
info, _ := f.Fs.Stat(backupPath)
if info != nil {
if info.IsDir() {
_ = f.DeleteDir(backupPath)
} else {
_ = f.DeleteFile(backupPath)
}
}
_ = f.CreateDir(backupPath, 0755)
if err := f.Copy(src, backupPath); err != nil {
return backupPath, err
}
return backupPath, nil
}
func ZipFile(files []archiver.File, dst afero.File) error {
zw := zip.NewWriter(dst)
defer zw.Close()
+8 -7
View File
@@ -3,6 +3,7 @@ package files
import (
"bufio"
"github.com/spf13/afero"
"io"
"net/http"
"os"
"os/user"
@@ -87,16 +88,20 @@ func ReadFileByLine(filename string, page, pageSize int) ([]string, bool, error)
}
defer file.Close()
scanner := bufio.NewScanner(file)
reader := bufio.NewReaderSize(file, 8192)
var lines []string
currentLine := 0
startLine := (page - 1) * pageSize
endLine := startLine + pageSize
for scanner.Scan() {
for {
line, _, err := reader.ReadLine()
if err == io.EOF {
break
}
if currentLine >= startLine && currentLine < endLine {
lines = append(lines, scanner.Text())
lines = append(lines, string(line))
}
currentLine++
if currentLine >= endLine {
@@ -104,10 +109,6 @@ func ReadFileByLine(filename string, page, pageSize int) ([]string, bool, error)
}
}
if err := scanner.Err(); err != nil {
return nil, false, err
}
isEndOfFile := currentLine < endLine
return lines, isEndOfFile, nil
+2 -2
View File
@@ -141,7 +141,7 @@ func (f *Ufw) Port(port FireInfo, operation string) error {
case "drop":
port.Strategy = "deny"
default:
return fmt.Errorf("unsupport strategy %s", port.Strategy)
return fmt.Errorf("unsupported strategy %s", port.Strategy)
}
if cmd.CheckIllegal(port.Protocol, port.Port) {
return buserr.New(constant.ErrCmdIllegal)
@@ -168,7 +168,7 @@ func (f *Ufw) RichRules(rule FireInfo, operation string) error {
case "drop":
rule.Strategy = "deny"
default:
return fmt.Errorf("unsupport strategy %s", rule.Strategy)
return fmt.Errorf("unsupported strategy %s", rule.Strategy)
}
if cmd.CheckIllegal(operation, rule.Protocol, rule.Address, rule.Port) {
-17
View File
@@ -17,20 +17,3 @@ func GetIniValue(filePath, Group, Key string) (string, error) {
}
return startKey.Value(), nil
}
func SetIniValue(filePath, Group, Key, value string) error {
cfg, err := ini.Load(filePath)
if err != nil {
return err
}
service, err := cfg.GetSection(Group)
if err != nil {
return err
}
targetKey := service.Key(Key)
if err != nil {
return err
}
targetKey.SetValue(value)
return cfg.SaveTo(filePath)
}
+2
View File
@@ -9,6 +9,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/mysql/client"
)
@@ -50,6 +51,7 @@ func NewMysqlClient(conn client.DBInfo) (MysqlClient, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(conn.Timeout)*time.Second)
defer cancel()
if err := db.PingContext(ctx); err != nil {
global.LOG.Errorf("test mysql conn failed, err: %v", err)
return nil, err
}
if ctx.Err() == context.DeadlineExceeded {
+19 -29
View File
@@ -5,6 +5,7 @@ import (
"crypto/x509"
"errors"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/go-sql-driver/mysql"
)
@@ -69,6 +70,8 @@ type AccessChangeInfo struct {
type BackupInfo struct {
Name string `json:"name"`
Type string `json:"type"`
Version string `json:"version"`
Format string `json:"format"`
TargetDir string `json:"targetDir"`
FileName string `json:"fileName"`
@@ -78,6 +81,8 @@ type BackupInfo struct {
type RecoverInfo struct {
Name string `json:"name"`
Type string `json:"type"`
Version string `json:"version"`
Format string `json:"format"`
SourceFile string `json:"sourceFile"`
@@ -101,45 +106,30 @@ var formatMap = map[string]string{
"big5": "big5_chinese_ci",
}
func VerifyPeerCertFunc(pool *x509.CertPool) func([][]byte, [][]*x509.Certificate) error {
return func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
if len(rawCerts) == 0 {
return errors.New("no certificates available to verify")
}
cert, err := x509.ParseCertificate(rawCerts[0])
if err != nil {
return err
}
opts := x509.VerifyOptions{Roots: pool}
if _, err = cert.Verify(opts); err != nil {
return err
}
return nil
}
}
func ConnWithSSL(ssl, skipVerify bool, clientKey, clientCert, rootCert string) (string, error) {
if !ssl {
return "", nil
}
pool := x509.NewCertPool()
tlsConfig := &tls.Config{
InsecureSkipVerify: skipVerify,
}
if len(rootCert) != 0 {
pool := x509.NewCertPool()
if ok := pool.AppendCertsFromPEM([]byte(rootCert)); !ok {
global.LOG.Error("append certs from pem failed")
return "", errors.New("unable to append root cert to pool")
}
tlsConfig.RootCAs = pool
}
cert, err := tls.X509KeyPair([]byte(clientCert), []byte(clientKey))
if err != nil {
return "", err
if len(clientCert) != 0 && len(clientKey) != 0 {
cert, err := tls.X509KeyPair([]byte(clientCert), []byte(clientKey))
if err != nil {
return "", err
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
if err := mysql.RegisterTLSConfig("cloudsql", &tls.Config{
RootCAs: pool,
Certificates: []tls.Certificate{cert},
InsecureSkipVerify: skipVerify,
VerifyPeerCertificate: VerifyPeerCertFunc(pool),
}); err != nil {
if err := mysql.RegisterTLSConfig("cloudsql", tlsConfig); err != nil {
global.LOG.Errorf("register tls config failed, err: %v", err)
return "", err
}
return "&tls=cloudsql", nil
+90 -37
View File
@@ -1,11 +1,14 @@
package client
import (
"compress/gzip"
"context"
"database/sql"
"errors"
"fmt"
"os"
"os/exec"
"path"
"strings"
"time"
@@ -13,7 +16,8 @@ import (
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/1Panel-dev/1Panel/backend/utils/mysql/helper"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
)
type Remote struct {
@@ -229,56 +233,63 @@ func (r *Remote) Backup(info BackupInfo) error {
return fmt.Errorf("mkdir %s failed, err: %v", info.TargetDir, err)
}
}
fileNameItem := info.TargetDir + "/" + strings.TrimSuffix(info.FileName, ".gz")
tlsItem, err := ConnWithSSL(r.SSL, r.SkipVerify, r.ClientKey, r.ClientCert, r.RootCert)
outfile, _ := os.OpenFile(path.Join(info.TargetDir, info.FileName), os.O_RDWR|os.O_CREATE, 0755)
global.LOG.Infof("start to mysqldump | gzip > %s.gzip", info.TargetDir+"/"+info.FileName)
image, err := loadImage(info.Type, info.Version)
if err != nil {
return err
}
dns := fmt.Sprintf("%s:%s@tcp(%s:%v)/%s?charset=%s&parseTime=true&loc=Asia%sShanghai%s", r.User, r.Password, r.Address, r.Port, info.Name, info.Format, "%2F", tlsItem)
backupCmd := fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'mysqldump -h %s -P %d -u%s -p%s %s --default-character-set=%s %s'",
image, r.Address, r.Port, r.User, r.Password, sslSkip(info.Version), info.Format, info.Name)
f, _ := os.OpenFile(fileNameItem, os.O_RDWR|os.O_CREATE, 0755)
defer f.Close()
if err := helper.Dump(dns, helper.WithData(), helper.WithDropTable(), helper.WithWriter(f)); err != nil {
return err
}
global.LOG.Debug(backupCmd)
cmd := exec.Command("bash", "-c", backupCmd)
gzipCmd := exec.Command("gzip", fileNameItem)
stdout, err := gzipCmd.CombinedOutput()
if err != nil {
return fmt.Errorf("gzip file %s failed, stdout: %v, err: %v", strings.TrimSuffix(info.FileName, ".gz"), string(stdout), err)
}
gzipCmd := exec.Command("gzip", "-cf")
gzipCmd.Stdin, _ = cmd.StdoutPipe()
gzipCmd.Stdout = outfile
_ = gzipCmd.Start()
_ = cmd.Run()
_ = gzipCmd.Wait()
return nil
}
func (r *Remote) Recover(info RecoverInfo) error {
fileName := info.SourceFile
if strings.HasSuffix(info.SourceFile, ".sql.gz") {
fileName = strings.TrimSuffix(info.SourceFile, ".gz")
gzipCmd := exec.Command("gunzip", info.SourceFile)
stdout, err := gzipCmd.CombinedOutput()
if err != nil {
return fmt.Errorf("gunzip file %s failed, stdout: %v, err: %v", info.SourceFile, string(stdout), err)
}
defer func() {
gzipCmd := exec.Command("gzip", fileName)
_, _ = gzipCmd.CombinedOutput()
}()
}
tlsItem, err := ConnWithSSL(r.SSL, r.SkipVerify, r.ClientKey, r.ClientCert, r.RootCert)
if err != nil {
return err
}
dns := fmt.Sprintf("%s:%s@tcp(%s:%v)/%s?charset=%s&parseTime=true&loc=Asia%sShanghai%s", r.User, r.Password, r.Address, r.Port, info.Name, info.Format, "%2F", tlsItem)
fi, _ := os.Open(info.SourceFile)
defer fi.Close()
f, err := os.Open(fileName)
image, err := loadImage(info.Type, info.Version)
if err != nil {
return err
}
defer f.Close()
if err := helper.Source(dns, f, helper.WithMergeInsert(1000)); err != nil {
return err
recoverCmd := fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'mysql -h %s -P %d -u%s -p%s %s --default-character-set=%s %s'",
image, r.Address, r.Port, r.User, r.Password, sslSkip(info.Version), info.Format, info.Name)
global.LOG.Debug(recoverCmd)
cmd := exec.Command("bash", "-c", recoverCmd)
if strings.HasSuffix(info.SourceFile, ".gz") {
gzipFile, err := os.Open(info.SourceFile)
if err != nil {
return err
}
defer gzipFile.Close()
gzipReader, err := gzip.NewReader(gzipFile)
if err != nil {
return err
}
defer gzipReader.Close()
cmd.Stdin = gzipReader
} else {
cmd.Stdin = fi
}
stdout, err := cmd.CombinedOutput()
stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "")
if err != nil || strings.HasPrefix(string(stdStr), "ERROR ") {
return errors.New(stdStr)
}
return nil
}
@@ -389,3 +400,45 @@ func (r *Remote) ExecSQLForHosts(timeout uint) ([]string, error) {
}
return rows, nil
}
func loadImage(dbType, version string) (string, error) {
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
fmt.Println(err)
}
images, err := cli.ImageList(context.Background(), types.ImageListOptions{})
if err != nil {
fmt.Println(err)
}
for _, image := range images {
for _, tag := range image.RepoTags {
if !strings.HasPrefix(tag, dbType+":") {
continue
}
if dbType == "mariadb" && strings.HasPrefix(tag, "mariadb:") {
return tag, nil
}
if strings.HasPrefix(version, "5.6") && strings.HasPrefix(tag, "mysql:5.6") {
return tag, nil
}
if strings.HasPrefix(version, "5.7") && strings.HasPrefix(tag, "mysql:5.7") {
return tag, nil
}
if strings.HasPrefix(version, "8.") && strings.HasPrefix(tag, "mysql:8.") {
return tag, nil
}
}
}
if dbType == "mariadb" || version == "8.x" {
return "mysql:8.2.0", nil
}
return "mysql:" + version, nil
}
func sslSkip(version string) string {
if strings.HasPrefix(version, "5.6") || strings.HasPrefix(version, "5.7") {
return "--skip-ssl"
}
return "--ssl-mode=DISABLED"
}
+1 -13
View File
@@ -38,18 +38,6 @@ func WithData() DumpOption {
}
}
func WithTables(tables ...string) DumpOption {
return func(option *dumpOption) {
option.tables = tables
}
}
func WithAllTable() DumpOption {
return func(option *dumpOption) {
option.isAllTable = true
}
}
func WithWriter(writer io.Writer) DumpOption {
return func(option *dumpOption) {
option.writer = writer
@@ -152,7 +140,7 @@ func Dump(dns string, opts ...DumpOption) error {
var columns []string
columns, err = lineRows.Columns()
if err != nil {
global.LOG.Errorf("get columes falied, err: %v", err)
global.LOG.Errorf("get columes failed, err: %v", err)
return err
}
columnTypes, err := lineRows.ColumnTypes()
-12
View File
@@ -19,24 +19,12 @@ type sourceOption struct {
}
type SourceOption func(*sourceOption)
func WithDryRun() SourceOption {
return func(o *sourceOption) {
o.dryRun = true
}
}
func WithMergeInsert(size int) SourceOption {
return func(o *sourceOption) {
o.mergeInsert = size
}
}
func WithDebug() SourceOption {
return func(o *sourceOption) {
o.debug = true
}
}
type dbWrapper struct {
DB *sql.DB
debug bool
+9 -3
View File
@@ -1,9 +1,11 @@
package components
type Block struct {
Line int
Comment string
Directives []IDirective
Line int
Comment string
Directives []IDirective
IsLuaBlock bool
LiteralCode string
}
func (b *Block) GetDirectives() []IDirective {
@@ -18,6 +20,10 @@ func (b *Block) GetLine() int {
return b.Line
}
func (b *Block) GetCodeBlock() string {
return b.LiteralCode
}
func (b *Block) FindDirectives(directiveName string) []IDirective {
directives := make([]IDirective, 0)
for _, directive := range b.GetDirectives() {
-13
View File
@@ -5,19 +5,6 @@ type Config struct {
FilePath string
}
func (c *Config) FindDirectives(directiveName string) []IDirective {
return c.Block.FindDirectives(directiveName)
}
func (c *Config) FindUpstreams() []*Upstream {
var upstreams []*Upstream
directives := c.Block.FindDirectives("upstream")
for _, directive := range directives {
upstreams = append(upstreams, directive.(*Upstream))
}
return upstreams
}
func (c *Config) FindServers() []*Server {
var servers []*Server
directives := c.Block.FindDirectives("server")
+4
View File
@@ -11,6 +11,10 @@ type Http struct {
Line int
}
func (h *Http) GetCodeBlock() string {
return ""
}
func (h *Http) GetComment() string {
return h.Comment
}
@@ -22,6 +22,10 @@ type Location struct {
Replaces map[string]string
}
func (l *Location) GetCodeBlock() string {
return ""
}
func NewLocation(directive IDirective) *Location {
location := &Location{
Modifier: "",
+120
View File
@@ -0,0 +1,120 @@
package components
import (
"fmt"
)
type LuaBlock struct {
Directives []IDirective
Name string
Comment string
LuaCode string
Line int
}
func NewLuaBlock(directive IDirective) (*LuaBlock, error) {
if block := directive.GetBlock(); block != nil {
lb := &LuaBlock{
Directives: []IDirective{},
Name: directive.GetName(),
LuaCode: block.GetCodeBlock(),
}
lb.Directives = append(lb.Directives, block.GetDirectives()...)
return lb, nil
}
return nil, fmt.Errorf("%s must have a block", directive.GetName())
}
func (lb *LuaBlock) GetName() string {
return lb.Name
}
func (lb *LuaBlock) GetParameters() []string {
return []string{}
}
func (lb *LuaBlock) GetDirectives() []IDirective {
directives := make([]IDirective, 0)
directives = append(directives, lb.Directives...)
return directives
}
func (lb *LuaBlock) FindDirectives(directiveName string) []IDirective {
directives := make([]IDirective, 0)
for _, directive := range lb.GetDirectives() {
if directive.GetName() == directiveName {
directives = append(directives, directive)
}
if directive.GetBlock() != nil {
directives = append(directives, directive.GetBlock().FindDirectives(directiveName)...)
}
}
return directives
}
func (lb *LuaBlock) GetCodeBlock() string {
return lb.LuaCode
}
func (lb *LuaBlock) GetBlock() IBlock {
return lb
}
func (lb *LuaBlock) GetComment() string {
return lb.Comment
}
func (lb *LuaBlock) RemoveDirective(key string, params []string) {
directives := lb.Directives
var newDirectives []IDirective
for _, dir := range directives {
if dir.GetName() == key {
if len(params) > 0 {
oldParams := dir.GetParameters()
if oldParams[0] == params[0] {
continue
}
} else {
continue
}
}
newDirectives = append(newDirectives, dir)
}
lb.Directives = newDirectives
}
func (lb *LuaBlock) UpdateDirective(key string, params []string) {
if key == "" || len(params) == 0 {
return
}
directives := lb.Directives
index := -1
for i, dir := range directives {
if dir.GetName() == key {
if IsRepeatKey(key) {
oldParams := dir.GetParameters()
if !(len(oldParams) > 0 && oldParams[0] == params[0]) {
continue
}
}
index = i
break
}
}
newDirective := &Directive{
Name: key,
Parameters: params,
}
if index > -1 {
directives[index] = newDirective
} else {
directives = append(directives, newDirective)
}
lb.Directives = directives
}
func (lb *LuaBlock) GetLine() int {
return lb.Line
}
+4
View File
@@ -11,6 +11,10 @@ type Server struct {
Line int
}
func (s *Server) GetCodeBlock() string {
return ""
}
func NewServer(directive IDirective) (*Server, error) {
server := &Server{}
if block := directive.GetBlock(); block != nil {
@@ -7,6 +7,7 @@ type IBlock interface {
UpdateDirective(name string, params []string)
GetComment() string
GetLine() int
GetCodeBlock() string
}
type IDirective interface {
@@ -12,6 +12,10 @@ type Upstream struct {
Line int
}
func (us *Upstream) GetCodeBlock() string {
return ""
}
func (us *Upstream) GetName() string {
return "upstream"
}
+15
View File
@@ -65,6 +65,21 @@ func DumpDirective(d components.IDirective, style *Style) string {
func DumpBlock(b components.IBlock, style *Style, startLine int) string {
var buf bytes.Buffer
if b.GetCodeBlock() != "" {
luaLines := strings.Split(b.GetCodeBlock(), "\n")
for i, line := range luaLines {
if strings.Replace(line, " ", "", -1) == "" {
continue
}
buf.WriteString(line)
if i != len(luaLines)-1 {
buf.WriteString("\n")
}
}
return buf.String()
}
line := startLine
if b.GetLine() > startLine {
for i := 0; i < b.GetLine()-startLine; i++ {
-14
View File
@@ -1,14 +0,0 @@
package nginx
import (
"github.com/1Panel-dev/1Panel/backend/utils/nginx/components"
"github.com/1Panel-dev/1Panel/backend/utils/nginx/parser"
)
func GetConfig(path string) (*components.Config, error) {
p, err := parser.NewParser(path)
if err != nil {
return nil, err
}
return p.Parse(), nil
}
-21
View File
@@ -1,21 +0,0 @@
package nginx
import (
"fmt"
"testing"
)
func TestNginx(t *testing.T) {
//config, err := GetConfig("/opt/1Panel/data/apps/nginx/nginx-1/conf/conf.d/word-1.conf")
config, err := GetConfig("/opt/1Panel/data/apps/nginx/nginx-new/conf/conf.d/1panel.cloud.conf")
if err != nil {
panic(err)
}
//server := config.FindServers()[0]
//fmt.Println(server)
//serverD := config.FindServers()[0]
//serverD.AddListen("8989", false)
fmt.Println(DumpConfig(config, IndentedStyle))
}
+1 -24
View File
@@ -1,9 +1,5 @@
package flag
import (
"fmt"
)
type Type int
const (
@@ -18,6 +14,7 @@ const (
Comment
Illegal
Regex
LuaCode
)
var (
@@ -46,33 +43,13 @@ type Flag struct {
Column int
}
func (t Flag) String() string {
return fmt.Sprintf("{Type:%s,Literal:\"%s\",Line:%d,Column:%d}", t.Type, t.Literal, t.Line, t.Column)
}
func (t Flag) Lit(literal string) Flag {
t.Literal = literal
return t
}
func (t Flag) EqualTo(t2 Flag) bool {
return t.Type == t2.Type && t.Literal == t2.Literal
}
type Flags []Flag
func (fs Flags) EqualTo(flags Flags) bool {
if len(fs) != len(flags) {
return false
}
for i, t := range fs {
if !t.EqualTo(flags[i]) {
return false
}
}
return true
}
func (t Flag) Is(typ Type) bool {
return t.Type == typ
}
+80 -34
View File
@@ -3,17 +3,18 @@ package parser
import (
"bufio"
"bytes"
"fmt"
"github.com/1Panel-dev/1Panel/backend/utils/nginx/parser/flag"
"io"
"strings"
)
type lexer struct {
reader *bufio.Reader
file string
line int
column int
Latest flag.Flag
reader *bufio.Reader
file string
line int
column int
inLuaBlock bool
Latest flag.Flag
}
func lex(content string) *lexer {
@@ -45,6 +46,11 @@ func (s *lexer) scan() flag.Flag {
//}
func (s *lexer) getNextFlag() flag.Flag {
if s.inLuaBlock {
s.inLuaBlock = false
flag := s.scanLuaCode()
return flag
}
retoFlag:
ch := s.peek()
switch {
@@ -56,6 +62,9 @@ retoFlag:
case ch == ';':
return s.NewToken(flag.Semicolon).Lit(string(s.read()))
case ch == '{':
if isLuaBlock(s.Latest) {
s.inLuaBlock = true
}
return s.NewToken(flag.BlockStart).Lit(string(s.read()))
case ch == '}':
return s.NewToken(flag.BlockEnd).Lit(string(s.read()))
@@ -70,6 +79,35 @@ retoFlag:
}
}
func (s *lexer) scanLuaCode() flag.Flag {
ret := s.NewToken(flag.LuaCode)
stack := make([]rune, 0, 50)
code := strings.Builder{}
for {
ch := s.read()
if ch == rune(flag.EOF) {
panic("unexpected end of file while scanning a string, maybe an unclosed lua code?")
}
if ch == '#' {
code.WriteRune(ch)
code.WriteString(s.readUntil(isEndOfLine))
continue
} else if ch == '}' {
if len(stack) == 0 {
_ = s.reader.UnreadRune()
return ret.Lit(strings.TrimRight(strings.Trim(code.String(), "\n"), "\n "))
}
if stack[len(stack)-1] == '{' {
stack = stack[0 : len(stack)-1]
}
} else if ch == '{' {
stack = append(stack, ch)
}
code.WriteRune(ch)
}
}
func (s *lexer) peek() rune {
r, _, _ := s.reader.ReadRune()
_ = s.reader.UnreadRune()
@@ -128,7 +166,7 @@ func (s *lexer) scanComment() flag.Flag {
func (s *lexer) scanQuotedString(delimiter rune) flag.Flag {
var buf bytes.Buffer
tok := s.NewToken(flag.QuotedString)
buf.WriteRune(s.read()) //consume delimiter
_, _ = buf.WriteRune(s.read())
for {
ch := s.read()
@@ -136,29 +174,13 @@ func (s *lexer) scanQuotedString(delimiter rune) flag.Flag {
panic("unexpected end of file while scanning a string, maybe an unclosed quote?")
}
if ch == '\\' {
if needsEscape(s.peek(), delimiter) {
nextch := s.read()
switch nextch {
case 'n':
fmt.Println("n")
buf.WriteRune('\n')
case 'r':
fmt.Println("r")
buf.WriteRune('\r')
case 't':
fmt.Println("t")
buf.WriteRune('\t')
case '\\':
buf.WriteRune('\\')
default:
buf.WriteRune('\\')
buf.WriteRune(nextch)
}
continue
}
if ch == '\\' && (s.peek() == delimiter) {
buf.WriteRune(ch)
buf.WriteRune(s.read())
continue
}
buf.WriteRune(ch)
_, _ = buf.WriteRune(ch)
if ch == delimiter {
break
}
@@ -168,7 +190,31 @@ func (s *lexer) scanQuotedString(delimiter rune) flag.Flag {
}
func (s *lexer) scanKeyword() flag.Flag {
return s.NewToken(flag.Keyword).Lit(s.readUntil(isKeywordTerminator))
var buf bytes.Buffer
tok := s.NewToken(flag.Keyword)
prev := s.read()
buf.WriteRune(prev)
for {
ch := s.peek()
if isSpace(ch) || isEOF(ch) || ch == ';' {
break
}
if ch == '{' {
if prev == '$' {
buf.WriteString(s.readUntil(func(r rune) bool {
return r == '}'
}))
buf.WriteRune(s.read()) //consume latest '}'
} else {
break
}
}
buf.WriteRune(s.read())
}
return tok.Lit(buf.String())
}
func (s *lexer) scanVariable() flag.Flag {
@@ -198,10 +244,6 @@ func isKeywordTerminator(ch rune) bool {
return isSpace(ch) || isEndOfLine(ch) || ch == '{' || ch == ';'
}
func needsEscape(ch, delimiter rune) bool {
return ch == delimiter || ch == 'n' || ch == 't' || ch == '\\' || ch == 'r'
}
func isSpace(ch rune) bool {
return ch == ' ' || ch == '\t' || isEndOfLine(ch)
}
@@ -213,3 +255,7 @@ func isEOF(ch rune) bool {
func isEndOfLine(ch rune) bool {
return ch == '\r' || ch == '\n'
}
func isLuaBlock(t flag.Flag) bool {
return t.Type == flag.Keyword && strings.HasSuffix(t.Literal, "_by_lua_block")
}

Some files were not shown because too many files have changed in this diff Show More