Compare commits

...
295 Commits
Author SHA1 Message Date
zhengkunwang 04edce94f9 feat: PHP 运行环境增加扩展源选择 (#2340) 2023-09-27 10:44:04 +08:00
ssongliu ccb81365f2 fix: 优化部分日志打印 (#2349) 2023-09-26 23:31:08 +08:00
ssongliu acf608cb7e fix: 解决部分容器创建失败的问题 (#2348) 2023-09-26 23:31:01 +08:00
zhengkunwang 2724708539 fix: 解决 Supervisor 停止之后一直同步状态的问题 (#2346) 2023-09-26 23:30:56 +08:00
zhengkunwang 50589a8d8d fix: 解决网站设置 PHP 源文之后没有立即生效的问题 (#2341) 2023-09-26 23:30:48 +08:00
ssongliu f06f487e8a fix: 修改容器操作提示信息 (#2324) 2023-09-16 10:02:16 +00:00
ssongliu 42dd668e1e fix: 解决恢复应用导致数据库备份文件丢失的问题 (#2323) 2023-09-16 09:00:16 +00:00
ssongliu 6cd00a072d fix: 优化版本排序 (#2322) 2023-09-16 08:30:16 +00:00
ssongliu 8faa3b6e51 fix: 修改数据库设置跳转路由 (#2320) 2023-09-16 06:36:15 +00:00
ssongliu 52198e9c0c fix: 数据库部分逻辑调整 (#2316) 2023-09-16 05:16:15 +00:00
ssongliu 173e3e9cc1 fix: 修改数据库备份逻辑 (#2315) 2023-09-15 10:48:14 +00:00
ssongliu 40c751cde2 fix: 修改数据库删除逻辑 (#2314) 2023-09-15 10:38:15 +00:00
zhengkunwang a3bf4513e1 fix: 修复日志文件下载不全的问题 (#2313) 2023-09-15 10:02:16 +00:00
zhengkunwang 4e7350aaae feat: 修改应用安装删除数据库逻辑 (#2311) 2023-09-15 08:46:13 +00:00
ssongliu 9ae935d06a fix: 解决数据库性能调整导致服务重启的问题 (#2308)
Refs #2301
2023-09-15 07:28:15 +00:00
ssongliu b6055eb058 fix: 解决数据冲突导致的升级失败问题 (#2306)
Refs #2290
2023-09-15 07:26:19 +00:00
wangdan-fit2cloud 0e158ffa2b fix: 修复导航样式冲突 (#2307)
#### What this PR does / why we need it?

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-09-15 07:24:20 +00:00
zhengkunwang cc7260e121 style: 容器界面提示文字和操作保持一致 (#2305) 2023-09-15 07:22:18 +00:00
ssongliu 43b3ef47f3 fix: 解决数据库列表样式错乱的问题 (#2296) 2023-09-14 14:50:13 +00:00
zhengkunwang 21755223e1 fix: 解决限制安装的应用程序在首页还能继续安装的问题 (#2295) 2023-09-14 14:48:14 +00:00
ssongliu 08f0147d81 fix: 解决防火墙偶发的修改备注信息失败的问题 (#2292) 2023-09-14 14:44:14 +00:00
ssongliu 3bcb5800a2 fix: 解决快照恢复时应用重建无法拉起的问题 (#2287) 2023-09-14 06:06:13 +00:00
ssongliu aa057772dc fix: 解决同步计划任务中快照失败的问题 (#2286) 2023-09-14 03:38:12 +00:00
ssongliu bf32424458 fix: 解决 MySQL 8.x 切换数据库访问权限导致创建数据库失败的问题 (#2283) 2023-09-14 02:58:14 +00:00
ssongliu 5c0fb405d0 fix: 修改计划任务下载判断 (#2284) 2023-09-14 02:54:17 +00:00
ssongliu 279f8f0e6c fix: 解决 MySQL 8.x 切换数据库访问权限导致创建数据库失败的问题 (#2281) 2023-09-13 10:30:14 +00:00
zhengkunwang 8bc6b409d9 fix: 解决 mariadb 删除没有同步删除数据库表的问题 (#2280) 2023-09-13 10:18:14 +00:00
zhengkunwang 5a315a773a fix: 解决 mariadb 删除没有同步删除数据库表的问题 (#2279) 2023-09-13 09:58:14 +00:00
ssongliu b9befc72a3 fix: 解决计划任务备份所有应用失败的问题 (#2278) 2023-09-13 09:32:12 +00:00
ssongliu cab0c7938d fix: 解决容器切换端口选项时,仍然进行端口校验的问题 (#2277) 2023-09-13 09:12:14 +00:00
zhengkunwang a241fb32d2 style: 修改 openresty 非 80 443 端口提示 (#2276) 2023-09-13 08:58:15 +00:00
ssongliu 7b4b0bddff feat: 升级时暂停计划任务 (#2274) 2023-09-13 07:54:12 +00:00
zhengkunwang 65662b2e84 feat: 取消 HTTP 模式下申请证书的 Openresty 端口校验 (#2272) 2023-09-13 07:50:12 +00:00
ssongliu 6f250e63db fix: 解决 redis 修改密码报错的问题 (#2271) 2023-09-13 06:10:11 +00:00
ssongliu 47cfa0c730 fix: 解决快照上传失败的问题 (#2270) 2023-09-13 06:08:16 +00:00
wanghe-fit2cloud 9837c60a88 refactor: update readme 2023-09-12 22:43:08 +08:00
ssongliu e991bbe799 fix: 修改防火墙操作超时时间 (#2268) 2023-09-12 14:40:12 +00:00
zhengkunwang 418863543c fix: 解决应用停止之后不能启动的问题 (#2267) 2023-09-12 09:52:11 +00:00
Ikko Eltociear Ashimine c027ea21ca docs: Add Japanese README (#2265) 2023-09-12 17:50:37 +08:00
ssongliu 327e160ea3 fix: 解决应用、网站备份恢复失败的问题 (#2266) 2023-09-12 09:48:11 +00:00
ssongliu 3a0b3e952b fix: 容器列表页显示端口映射对应 ip (#2262) 2023-09-12 09:16:10 +00:00
ssongliu 16446ecaf3 feat: 优化防火墙响应速度 (#2261) 2023-09-12 09:14:15 +00:00
zhengkunwang ecb7a2ebee fix: 解决应用安装选择 mariadb 安装失败的问题 (#2264) 2023-09-12 09:12:11 +00:00
ssongliu 49d217e990 fix: 解决数据库切换导致的错乱样式问题 (#2259) 2023-09-12 02:54:10 +00:00
ssongliu d1d0ca92ca feat: 支持自定义概览页安全入口提示是否显示 (#2257) 2023-09-11 10:32:11 +00:00
ssongliu ac91480987 fix: 解决防火墙搜索报错的问题 (#2256) 2023-09-11 10:30:14 +00:00
ssongliu a364dbffbf fix: 数据库、计划任务下拉框样式调整 (#2255) 2023-09-11 10:28:19 +00:00
ssongliu 9aaf3bf435 fix: 解决不同 mysql 应用无法同名的问题 (#2252) 2023-09-11 06:56:10 +00:00
ssongliu eff3e8fcc5 fix: 数据库选择项样式优化 (#2235) 2023-09-10 09:20:13 +00:00
ssongliu 9d11e56074 fix: 修改 mongodb 和 postgresql 的数据库用户名获取 (#2232) 2023-09-10 09:18:16 +00:00
ssongliu 645e6bbb79 fix: 快照增加文件大小显示 (#2227) 2023-09-08 15:12:13 +00:00
ssongliu 9e5a452245 fix: 修改数据库配置文件不存在提示信息 (#2226) 2023-09-08 15:10:17 +00:00
ssongliu 6b908d2553 fix: 解决数据库创建重命名用户的问题 (#2222) 2023-09-07 13:36:10 +00:00
ssongliu 6e8f22a4a6 fix: 修改数据库创建权限校验规则 (#2220) 2023-09-07 09:42:10 +00:00
zhengkunwang f812d9f7cd fix: 解决删除 mariadb 的时候没有删除数据库表信息的问题 (#2219) 2023-09-07 09:20:10 +00:00
ssongliu a61487de9f fix: 数据库删除检查样式调整 (#2218) 2023-09-07 09:18:17 +00:00
ssongliu 9a43b52c62 fix: 容器创建增加 entrypoint 参数 (#2217) 2023-09-07 08:24:10 +00:00
ssongliu ad8bf09034 feat: 解决数据库同步时存在特殊字符导致失败的问题 (#2214) 2023-09-07 07:16:11 +00:00
zhengkunwang e75c9a0304 feat: 安装数据库应用增加与远程数据库名称校验 (#2212) 2023-09-07 03:38:10 +00:00
ssongliu c485fe2bd5 fix: 解决获取默认配置失败的问题 (#2208) 2023-09-06 13:22:12 +00:00
ssongliu add4fab794 feat: 数据库设置或远程服务器跳转返回时跳转到原路由 (#2207) 2023-09-06 13:20:16 +00:00
zhengkunwang 02dce0e3fa style: 修改部分弹窗 提示文字样式 (#2202) 2023-09-06 09:16:12 +00:00
ssongliu ae515b079c feat: 容器批量操作增加详情显示 (#2201) 2023-09-06 09:14:16 +00:00
ssongliu df5a1b3e40 fix: 登陆接口增加安全入口校验 (#2199) 2023-09-06 07:00:12 +00:00
zhengkunwang 278fac6d80 fix: 解决文件名带空格导致的设置权限失败的问题 (#2197)
Refs https://github.com/1Panel-dev/1Panel/issues/2149
2023-09-06 03:32:11 +00:00
ssongliu 3e33a4b16c fix: 修改数据库重复校验判断 (#2195) 2023-09-06 03:02:12 +00:00
ali-pay f0eaee82b6 fix: 解决路由按钮边框显示异常的问题 (#2196) 2023-09-06 10:54:24 +08:00
zhengkunwang 4d8118d9ab feat:网站目录支持三级 增加网站目录权限校验 (#2190)
Refs https://github.com/1Panel-dev/1Panel/issues/2140
2023-09-05 14:28:11 +00:00
ssongliu 31cd6691c9 feat: 容器日志行数增加所有选项 (#2191)
Refs #2143
2023-09-05 14:04:11 +00:00
zhengkunwang e509fb0571 feat:文件列表增加 uid 和 gid 显示 (#2192)
Refs https://github.com/1Panel-dev/1Panel/issues/2066
2023-09-05 10:34:11 +00:00
ssongliu e6522ded62 feat: MySQL 数据库选择项增加跳转 (#2189) 2023-09-05 09:34:11 +00:00
ssongliu 0a9fc26714 fix: 容器列表端口显示优化 (#2188)
Refs #2181
2023-09-05 09:32:15 +00:00
zhengkunwang ed3d587046 fix: 解决守护进程停止之后状态读取异常的问题 (#2186)
Refs https://github.com/1Panel-dev/1Panel/issues/2167
2023-09-05 08:12:10 +00:00
zhengkunwang 38b462eece style: 修改文件和应用商店样式 (#2184) 2023-09-05 06:20:10 +00:00
ssongliu c588c34955 fix: 容器列表样式调整 (#2179) 2023-09-04 14:24:14 +00:00
zhengkunwang fb00ba812a feat: 解决快速安装应用跳转失败问题 (#2178) 2023-09-04 14:22:18 +00:00
ssongliu 0c2d971d72 fix: 解决数据库切换时,状态标签显示不正常的问题 (#2177) 2023-09-04 09:40:14 +00:00
ssongliu ea17c451a6 fix: 解决合并导致的代码格式问题 (#2174) 2023-09-04 08:56:13 +00:00
zhengkunwang c00d715b87 feat: 解决安装应用失败的问题 (#2172) 2023-09-04 08:26:13 +00:00
ssongliu db443a9f82 fix: 修改 migration,解决升级失败的问题 (#2171) 2023-09-04 08:24:18 +00:00
zhengkunwang 5b00bfd1c4 feat: 优化应用安装流程 (#2168) 2023-09-04 07:58:13 +00:00
ssongliu dbf349ee9d fix: 解决防火墙筛选问题 (#2170) 2023-09-04 07:56:12 +00:00
CyJaySong f75744e252 🎨 容器列表添加网络地址列 (#2169) 2023-09-04 15:51:10 +08:00
ssongliu ecc444db9f fix: 修改远程数据库列表查询规则 (#2166) 2023-09-04 06:20:13 +00:00
wangdan-fit2cloud ba38402a24 fix: 调整按钮样式 (#2163)
#### What this PR does / why we need it?

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-09-04 06:18:17 +00:00
ali-pay 1a6d237d0f fix: 解决页面像素不对齐的问题 (#2165) 2023-09-04 12:53:09 +08:00
ssongliu 78999f97db fix: 统一状态栏样式 (#2162) 2023-09-04 04:04:13 +00:00
ssongliu 7dacac8846 feat: 数据库密码加密存储 (#2146) 2023-09-01 15:10:14 +00:00
ssongliu 0c6a065994 fix: 解决概览页流量突增问题 (#2142)
Refs #2073
2023-09-01 08:42:12 +00:00
ssongliu dba3c6f2c1 fix: 面板设置样式调整 (#2141)
Refs #2079
2023-09-01 07:54:12 +00:00
ssongliu 149d44dbbe feat: 远程数据库支持添加 mariadb (#2139) 2023-09-01 04:28:12 +00:00
ali-pay 66c824a841 fix: 解决侧边栏收起状态下菜单长度不一致的问题 (#2137) 2023-08-31 23:14:15 +08:00
ssongliu a948ece792 fix: 数据库样式调整 (#2136) 2023-08-31 14:28:13 +00:00
ssongliu 3da1b22a1b fix: 解决同步数据库失败的问题 (#2133) 2023-08-31 09:22:12 +00:00
zhengkunwang ff0ed46554 feat: 应用安装支持选择 mariadb 和其他 mysql (#2130) 2023-08-31 07:14:11 +00:00
ssongliu 2ed2a7ed8c fix: 解决 deepin 获取 ssh 服务状态失败的问题 (#2129)
Refs #1646
2023-08-31 06:16:12 +00:00
ssongliu f9d5a445c7 feat: 防火墙支持多 ip 及多 ip 段 (#2122)
Refs #2046
2023-08-30 14:28:12 +00:00
ssongliu 9cecd66f3d feat: 防火墙列表增加状态、策略筛选项 (#2121)
Refs #1859
2023-08-30 06:56:11 +00:00
ssongliu 529a7e6ea6 fix: 1pctl 增加回滚功能 (#2110)
Refs #1660
2023-08-30 03:06:10 +00:00
ssongliu d3cd087b91 feat: 防火墙支持删除系统占用端口 (#2116) 2023-08-30 02:30:09 +00:00
ssongliu 4d2ca1f3a0 fix: 1pctl 增加非 root 提示信息 (#2112)
Refs #2100
2023-08-30 02:28:14 +00:00
zhengkunwang 7b1566f4ac feat: 修改删除文件的提示 (#2111)
Refs https://github.com/1Panel-dev/1Panel/issues/2086
2023-08-29 14:24:13 +00:00
zhengkunwang 7891f9225b feat: 应用安装支持选择多数据库 (#2109) 2023-08-29 14:22:18 +00:00
ssongliu 03f53f7042 fix: 解决版本升级导致无法创建数据库的问题 (#2108) 2023-08-29 09:18:16 +00:00
ssongliu 9f38a78512 fix: 修改数据库 migration (#2105) 2023-08-29 06:36:16 +00:00
zhengkunwang 49e3d45912 fix: 解决 Openresty 获取状态异常的问题 (#2104) 2023-08-29 06:32:17 +00:00
zhengkunwang d1e7b36243 feat: 修改 user-info 指令的输出 (#2103) 2023-08-29 03:40:15 +00:00
wanghe 3adb200585 Update README.md (#2102) 2023-08-29 11:02:45 +08:00
ssongliu ff141b21e2 feat: 多数据库接口适配 (#2094) 2023-08-29 02:50:15 +00:00
Allyxandallyxmiko-server b54c79e196 fix: 修复压缩包解压时可能的由于父目录不存在导致的解压失败的问题 (#2099)
Co-authored-by: allyxmiko-server <allyxmiko@gmail.com>
2023-08-28 22:37:48 +08:00
zhengkunwang b55b930353 feat: PHP 运行环境更新增加重建 php 应用功能 (#2095) 2023-08-28 14:18:15 +00:00
zhengkunwang 8b1098fc9d feat: 应用重建改为响应式 (#2091) 2023-08-28 10:10:16 +00:00
zhengkunwang ac0f697111 fix: 解决多容器应用升级失败的问题 (#2090) 2023-08-28 08:18:13 +00:00
zhengkunwang 7ff9c75bdf feat: 修改应用商店版本升级判断条件 (#2089) 2023-08-28 07:32:12 +00:00
zhengkunwang 9f0d957145 feat: 网站日志支持读取 10M 以上文件 (#2072) 2023-08-25 15:42:14 +00:00
zhengkunwang 680e93dcd4 feat: 网站支持不同域名使用同一个端口 (#2071)
Refs https://github.com/1Panel-dev/1Panel/issues/1997
2023-08-25 15:40:17 +00:00
zhengkunwang f546ffee90 feat: 应用商店增加版本控制 (#2068) 2023-08-25 10:44:11 +00:00
ssongliu 6a8bd490bf feat: 计划任务支持系统快照 (#2061)
Refs #1503 #1480 #1291
2023-08-24 14:26:16 +00:00
zhengkunwang 684f20a5dc feat: Openresty 支持非 80 443 端口安装 (#2059) 2023-08-24 10:20:15 +00:00
zhengkunwang a3c07dd3d3 fix: 解决打开进程守护页面报错的问题 (#2057)
Refs https://github.com/1Panel-dev/1Panel/issues/2051
2023-08-24 08:16:45 +00:00
ssongliu ca0368c931 feat: 放开 1panel-network 按钮禁用 (#2052) 2023-08-24 04:02:13 +00:00
zhengkunwang 59eff6e27f feat: 应用安装、升级、卸载之前支持执行脚本 (#2042) 2023-08-23 14:48:14 +00:00
ssongliu 63ae17372d feat: 计划任务支持备份应用 (#2040)
Refs #1788 #1713
2023-08-23 14:44:14 +00:00
ssongliu f196d029cb feat: 重构快照功能,取消快照过程 loading (#2039) 2023-08-23 09:16:18 +00:00
zhengkunwang f9bf6b69fb feat: 增加强制删除 openresty 功能 (#2030) 2023-08-23 02:42:13 +00:00
ssongliu 5e47656542 fix: 解决数据库用户名显示错误的问题 (#2028) 2023-08-22 08:32:12 +00:00
zhengkunwang d72d2c1167 fix: 解决运行环境网站切换PHP版本导致已安装应用消失的问题 (#2026) 2023-08-22 07:38:11 +00:00
ssongliu 24c87905b1 fix: 解决清空容器日志导致的 CPU 占用异常问题 (#2019)
Refs #1999
2023-08-21 09:00:12 +00:00
ssongliu fe7fed02ee fix: 服务器地址支持设置为空 (#2016) 2023-08-21 08:14:12 +00:00
ssongliu f4c3aafced fix: 解决数据库用户名超长导致用户创建失败的问题 (#2011) 2023-08-21 03:32:11 +00:00
huailei c1db5886ff perf: 防止message重复弹出 (#1996) 2023-08-18 17:22:32 +08:00
ssongliu 5dac95c3de fix: 移除容器日志中的 ANSI 转义序列 (#1991)
Refs #1655
2023-08-17 12:24:09 +00:00
ssongliu f8f231253d fix: 解决防火墙多端口创建失败的问题 (#1988) 2023-08-17 08:58:10 +00:00
ssongliu da8a06909b fix: 延长进程状态获取等待时间 (#1986) 2023-08-17 08:10:09 +00:00
ssongliu e67d9055aa fix: 解决进程状态一直加载中的问题 (#1983)
Refs #1968
2023-08-17 06:46:09 +00:00
ssongliu c3134e4c2a fix: 解决网站反代编辑时回显错误的问题 (#1980)
Refs #1976
2023-08-17 03:32:09 +00:00
wangdan-fit2cloud bdd36b3fbc fix: 移动端样式优化 (#1979)
#### What this PR does / why we need it?

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-08-17 03:02:09 +00:00
ssongliu d04c4bd425 fix: 解决暂停进程被重新拉起的问题 (#1977)
Refs #1964
2023-08-16 15:44:11 +00:00
ssongliu 8135f0f598 fix: 解决获取远程数据库失败的问题 (#1974)
Refs #1925
2023-08-16 15:36:11 +00:00
ssongliu 6be8bc0c50 fix: 解决容器编排创建日志加载慢的问题 (#1973)
Refs #1950
2023-08-16 15:34:15 +00:00
ssongliu 8433822021 fix: 修改网站重定向列表样式 (#1975)
Refs #1971
2023-08-16 15:32:12 +00:00
wangdan-fit2cloud 65194ae22b 优化移动端样式 (#1967)
#### What this PR does / why we need it?

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-08-16 03:34:10 +00:00
ssongliu 89faf01391 feat: 防火墙支持设置备注信息 (#1962)
Refs #1930 #1121 #1642
2023-08-15 14:48:11 +00:00
ssongliu a0456d0f7a fix: 进程守护区分项目名称和进程名称 (#1952) 2023-08-14 13:38:12 +00:00
ssongliu 35b8532eaa feat: 优化进程守护状态获取方式 (#1943) 2023-08-14 06:02:11 +00:00
zhengkunwang b78b0e67ac fix: 解决登录页切换语言提示信息不更换的问题 (#1939)
Refs https://github.com/1Panel-dev/1Panel/issues/1923
2023-08-14 05:56:11 +00:00
ssongliu 384fc84039 fix: 解决备份压缩报错问题 (#1938) 2023-08-12 22:48:11 +00:00
ssongliu 7ad6e607fe fix: 恢复表格封装代码 (#1937) 2023-08-12 15:24:13 +00:00
ssongliu 27100594a4 fix: 修改 loading (#1936) 2023-08-12 15:12:14 +00:00
ssongliu 0d687daa13 feat: 数据库连接信息增加复制按钮以及超长悬浮显示 (#1935) 2023-08-12 14:36:11 +00:00
ssongliu ad78edda64 fix: 备份文件时忽略文件变化 (#1934) 2023-08-12 13:54:11 +00:00
ssongliu 3e6cd1cab1 feat: 远程数据库增加连接性测试 (#1928)
Refs #1924
2023-08-11 14:20:15 +00:00
ssongliu 98df3806f5 fix: 优化容器清理提示信息 (#1927) 2023-08-11 14:18:17 +00:00
ssongliu 18e8af6234 fix: 容器终端断开连接时,保持抽屉打开 (#1926) 2023-08-11 08:08:14 +00:00
zhengkunwang 5bbda8f842 fix: 解决应用升级镜像拉取失败导致应用服务异常的问题 (#1921) 2023-08-11 07:52:13 +00:00
zhengkunwang 85f8c1e634 feat: 守护进程增加操作列 (#1919) 2023-08-11 07:50:13 +00:00
ssongliu 85c935ee46 fix: 容器创建启动命令提示信息修改 (#1917) 2023-08-11 06:04:12 +00:00
ssongliu b4033471e7 fix: 修改表格分页偶发的数据丢失问题 (#1911)
Refs #1834
2023-08-10 14:36:13 +00:00
ssongliu 8dca519068 feat: 远程数据库地址支持域名 (#1909) 2023-08-10 14:34:23 +00:00
ssongliu 39d8b0d98c fix: 数据库获取时,忽略重名数据库 (#1906) 2023-08-10 06:06:13 +00:00
zhengkunwang 278a562320 feat: 修改应用安装数据库名的默认值 (#1904) 2023-08-10 04:48:12 +00:00
zhengkunwang c0d8578466 fix: 解决 PHP 运行环境网站恢复失败的问题 (#1903) 2023-08-10 02:42:12 +00:00
ssongliu b8480e4928 fix: 解决计划任务编辑错误 (#1902) 2023-08-10 02:40:12 +00:00
ssongliu 1ff39d7b85 fix: 解决 ssh 登录日志分页错误 (#1901) 2023-08-10 02:38:16 +00:00
ssongliu b1f817f09b fix: 限制数据库名称大小写 (#1899) 2023-08-10 02:36:20 +00:00
ssongliu 018fb85bc3 fix: 计划任务创建样式调整 (#1897) 2023-08-09 10:12:12 +00:00
zhengkunwang bb7102d543 feat: 编辑运行环境增加提示 (#1896) 2023-08-09 10:04:12 +00:00
zhengkunwang 1cccfeff22 feat: 修改应用商店的分页 (#1895) 2023-08-09 10:00:13 +00:00
ssongliu 6c3a5b0ec7 fix: 修改数据库名称长度提示信息 (#1894) 2023-08-09 09:20:12 +00:00
ssongliu 3fffb3c8ec fix: 备份账号高度调整 (#1893) 2023-08-09 08:40:12 +00:00
ssongliu a51d2d15de fix: 快照恢复前先清空对应文件夹 (#1892) 2023-08-09 08:38:13 +00:00
wangdan-fit2cloud 1c0d0d46a7 Pr@dev@dan (#1891)
#### What this PR does / why we need it?

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-08-09 08:32:12 +00:00
zhengkunwang 65b8d47310 style: 修改 Supervisor 初始化的提示文字 (#1890) 2023-08-09 07:42:12 +00:00
ssongliu ca586bb766 fix: 快照前停止计划任务执行 (#1888) 2023-08-09 06:24:12 +00:00
ssongliu 25ccadea9e fix: 解决计划任务备份远程数据库详情显示错误的问题 (#1886) 2023-08-09 06:14:12 +00:00
zhengkunwang 87e9662af4 fix: 解决关闭高级设置之后没有提示端口放开的问题 (#1887) 2023-08-09 06:08:12 +00:00
zhengkunwang fe705a25ea feat: 优化网站反向代理地址填写 (#1885) 2023-08-09 03:40:12 +00:00
ssongliu 7e5cdbf953 fix: 解决编排创建日志获取失败的问题 (#1883) 2023-08-09 03:18:12 +00:00
zhengkunwang e262f4fa40 fix: 解决创建PHP运行环境报错的问题 (#1882) 2023-08-08 14:32:11 +00:00
ssongliu f4d5b5437e fix: 修改部分字段校验 (#1881) 2023-08-08 14:30:16 +00:00
zhengkunwang ce258cf157 fix: 解决 Mysql 恢复失败的问题 (#1874) 2023-08-08 09:06:12 +00:00
zhengkunwang 381233a8a5 feat: 修改 Supervisor 的判断 (#1876) 2023-08-08 09:00:12 +00:00
ssongliu 0e8a4eaf2e fix: 解决同步快照名称加载错误的问题 (#1875) 2023-08-08 08:44:12 +00:00
zhengkunwang 0c4400d6f7 feat: 进程守护状态增加信息列 (#1873) 2023-08-08 08:38:12 +00:00
ssongliu 075ae253a1 fix: 国际化问题修改 (#1872) 2023-08-08 06:48:11 +00:00
zhengkunwang fc57256197 style: 修改网站提示 (#1871) 2023-08-08 06:44:12 +00:00
zhengkunwang e0f15ca783 feat: 已安装应用增加 https 端口跳转 (#1870) 2023-08-08 06:12:11 +00:00
ssongliu 1931e8800a fix: 优化远程数据库备份方式 (#1869) 2023-08-08 06:08:11 +00:00
zhengkunwang fb4a5491eb fix: 解决 sftpgo 安装失败的问题 (#1866) 2023-08-08 06:06:20 +00:00
zhengkunwang cd77c672bc feat: 守护进程一些字段增加校验 (#1865) 2023-08-08 06:04:19 +00:00
ssongliu 9b02e88e3c fix: 防火墙与进程守护加载样式调整 (#1864) 2023-08-07 14:46:27 +00:00
ssongliu 2e73857b42 fix: 解决同步快照路径错误的问题 (#1863) 2023-08-07 07:50:28 +00:00
ssongliu 7721395748 fix: 修改快照版本判断 (#1862) 2023-08-07 07:10:26 +00:00
ssongliu c326606401 fix: 优化英文状态下,备份恢复按钮换行的问题 (#1860) 2023-08-07 03:52:11 +00:00
zhengkunwang c9ea6f6c8d fix: 解决应用安装有远程同名数据库报错的问题 (#1861) 2023-08-07 03:50:11 +00:00
zhengkunwang 8c2d3e432d fix: 解决 redis 页面命令行执行命令提示未认证的问题 (#1858) 2023-08-06 14:28:03 +00:00
ssongliu 20a47afb8c fix: mysql 数据库连接信息样式调整 (#1857) 2023-08-06 13:02:00 +00:00
zhengkunwang 07d5c580a6 fix: 解决网站路径重定向不保留URL参数不生效的问题 (#1855) 2023-08-06 09:50:01 +00:00
zhengkunwang 480b8acd66 feat:404重定向取消设置是否保留 URL 参数 (#1854) 2023-08-06 09:48:04 +00:00
zhengkunwang ac7e47bdf1 style: 修改初始化 Supervisor 的提示文字 (#1838) 2023-08-04 10:22:01 +00:00
ssongliu a516ba9d63 fix: 解决创建数据库用户重复的问题 (#1837) 2023-08-04 10:20:04 +00:00
zhengkunwang 5f357db1e1 fix: 解决重定向切换 404 没有消除名称字段校验结果的问题 (#1841) 2023-08-04 09:46:00 +00:00
zhengkunwang 5944f67823 fix: 解决文件上传可以拖入文件夹的问题 (#1840)
Refs https://github.com/1Panel-dev/1Panel/issues/1833
2023-08-04 09:44:04 +00:00
zhengkunwang aa1e548ddf feat: 网站反向代理增加协议选择 (#1832) 2023-08-04 09:42:09 +00:00
ssongliu 80e845f320 fix: 解决数据库同步后备份路径错误的问题 (#1830) 2023-08-04 06:10:43 +00:00
zhengkunwang d4e6232664 fix: 解决项目启动失败的问题 (#1829) 2023-08-03 14:13:59 +00:00
ssongliu 43e1ec0244 fix: 解决应用备份失败的问题 (#1828) 2023-08-03 10:40:23 +00:00
zhengkunwang e30546102e fix: 解决应用同步可能出现的多个同名应用的问题 (#1827) 2023-08-03 10:37:57 +00:00
zhengkunwang 70319aca45 feat: 应用关联 redis 时自动填充密码 (#1826) 2023-08-03 10:36:01 +00:00
ssongliu 7c6f8ff7c4 feat: 创建远程数据库支持 localhost 权限 (#1825) 2023-08-03 17:32:47 +08:00
zhengkunwang 7eb96a35df fix: 解决应用升级无法编辑最新配置的问题 (#1824) 2023-08-03 08:51:28 +00:00
ssongliu e52e6ddaa2 fix: 解决数据库日志监听未刷新的问题 (#1823) 2023-08-03 08:49:32 +00:00
zhengkunwang 40d3392520 fix: 解决 cloudreve 升级数据丢失的问题 (#1822) 2023-08-03 08:47:44 +00:00
ssongliu b83147220a fix: 分页排序样式调整 (#1821) 2023-08-03 08:21:27 +00:00
ssongliu a031d3ba41 fix: 计划任务数据库选项增加前缀 (#1820) 2023-08-03 08:19:30 +00:00
zhengkunwang df770460d6 feat: 应用安装适配 sftpgo pgadmin4 (#1819) 2023-08-03 07:25:26 +00:00
zhengkunwang 0f6e14d2f9 feat: Supervisor 增加修改配置功能 (#1818) 2023-08-03 07:23:32 +00:00
ssongliu b0320a4ebc feat: 数据库从服务器获取增加提示信息 (#1817) 2023-08-03 06:45:27 +00:00
ssongliu e4462c06fe fix: 应用恢复后自动同步数据库 (#1816) 2023-08-03 02:42:13 +00:00
ssongliu c7e7629d85 fix: 修改数据库密码校验规则 (#1815) 2023-08-03 02:40:17 +00:00
zhengkunwang d34e7492e1 fix: 修复网站配置文件读取漏洞 (#1814) 2023-08-02 14:38:32 +00:00
ssongliu f6b84d384e fix: 解决越权下载文件的问题 (#1813) 2023-08-02 14:36:37 +00:00
zhengkunwang 202a2ad7ae feat: Supervisor 适配 Debian 操作系统 (#1811) 2023-08-02 10:29:50 +00:00
ssongliu 6f6c836d9a fix: 解决打开越权读取文件的问题 (#1810) 2023-08-02 08:47:30 +00:00
zhengkunwang 01d5bd047f fix: 解决安装应用 数据库密码字段包含&$提示错误的问题 (#1809)
Refs https://github.com/1Panel-dev/1Panel/issues/1793
2023-08-02 03:38:16 +00:00
ssongliu f000f8b46c fix: 回滚目录切换 (#1808) 2023-08-01 14:05:14 +00:00
zhengkunwang ae8b6b8c05 style: 修改网站防盗链页面样式 (#1807) 2023-08-01 14:03:15 +00:00
zhengkunwang b7d09d0201 fix: 解决带数据库应用安装失败的问题 (#1806) 2023-08-01 10:44:54 +00:00
ssongliu 67bba4bc1d fix: 快照容器部分修改为仅备份已安装应用数据 (#1805) 2023-08-01 10:40:54 +00:00
zhengkunwang a0a26f237b fix: 解决重定向设置重定向到首页但是编辑显示错误的问题 (#1803) 2023-08-01 10:25:12 +00:00
zhengkunwang a84d8dd828 fix: 解决文件压缩无法选择文件夹的问题 (#1802) 2023-08-01 10:22:58 +00:00
zhengkunwang 39abd4341d feat: 增加守护进程管理 (#1800) 2023-08-01 09:31:42 +00:00
^薄荷布丁^ b31de5e637 修正翻译错误 (#1797) 2023-08-01 12:53:14 +08:00
ssongliu 09653f9c06 fix: 修改远程数据库依赖 (#1794) 2023-08-01 03:20:16 +00:00
ssongliu e0ca9507de fix: 修改监控默认采集间隔和保存时间 (#1795) 2023-07-31 14:06:37 +00:00
zhengkunwang b59ccc52ae feat: 增加进程守护管理 (#1786)
增加 Supervisor 状态读取 初始化 启动 重启 设置 日志 功能
Refs https://github.com/1Panel-dev/1Panel/issues/1754
Refs https://github.com/1Panel-dev/1Panel/issues/1409
Refs https://github.com/1Panel-dev/1Panel/issues/1388
Refs https://github.com/1Panel-dev/1Panel/issues/379
Refs https://github.com/1Panel-dev/1Panel/issues/353
Refs https://github.com/1Panel-dev/1Panel/issues/331
2023-07-31 03:28:41 +00:00
ssongliu f9c8aa0484 fix: 解决 ssh 登录日志年份获取失败的问题 (#1785) 2023-07-28 10:27:20 +00:00
ssongliu 5629dbff8e fix: 解决手动挂载的挂载卷在编辑时不显示的问题 (#1783)
Refs #1781
2023-07-28 10:26:02 +00:00
ssongliu ed0923fd28 fix: 面板设置支持设置服务器域名 (#1778) 2023-07-28 10:23:01 +00:00
ssongliu 489efb7ac1 fix: 创建抽屉隐藏名称显示 (#1777) 2023-07-28 10:21:06 +00:00
Eric_LeeandEric 6a0b4a1176 perf: 移除不必要的 ssh session 连接 (#1780)
* perf: 移除不必要的 ssh session 连接

---------

Co-authored-by: Eric <xplv@126.com>
2023-07-28 11:04:03 +08:00
ssongliu 7a67377aa9 fix: 兼容安装目录设置为根目录的情况 2023-07-27 16:42:12 +08:00
ssongliu 40aaa1ceb0 feat: 数据库实现远程服务器获取功能 (#1775) 2023-07-27 08:07:27 +00:00
ssongliu e83e592e0a feat: 实现远程数据备份恢复功能 (#1774) 2023-07-27 06:32:23 +00:00
ssongliu 87a7cf3aca fix: 解决容器端口冲突导致创建失败的问题 (#1770) 2023-07-27 05:58:13 +00:00
longyuanand邵传波 2a20ff0b79 fix DatabaseMysql 新增的 From字段 不允许为空 但未设置默认值 导致已存在数据库的1panel升级时无法正常启动 (#1772)
Co-authored-by: 邵传波 <shaocb@mail.unitid.cn>
2023-07-27 13:58:08 +08:00
zhengkunwang 1a6c45c526 feat: 文件列表记录最后一次位置 (#1753)
Refs https://github.com/1Panel-dev/1Panel/issues/1254
2023-07-25 09:26:12 +00:00
zhengkunwang 4d368a4de8 feat: 登录页增加语言选项 (#1752)
Refs https://github.com/1Panel-dev/1Panel/issues/1728
2023-07-25 09:24:15 +00:00
zhengkunwang b05e5736a6 feat: 升级应用增加备份选项 (#1750)
Refs https://github.com/1Panel-dev/1Panel/issues/1742
2023-07-25 09:22:20 +00:00
zhengkunwang 3b9f92e03f feat: PHP 网站增加版本切换 (#1748)
Refs https://github.com/1Panel-dev/1Panel/issues/1586
Refs https://github.com/1Panel-dev/1Panel/issues/1406
Refs https://github.com/1Panel-dev/1Panel/issues/974
Refs https://github.com/1Panel-dev/1Panel/issues/671
2023-07-25 09:20:25 +00:00
ssongliu da4c264908 feat: 增加远程数据库提示信息 (#1745) 2023-07-25 09:08:13 +00:00
ssongliu bbd649188a feat: 实现本地远程数据库切换 2023-07-24 14:58:55 +08:00
ssongliu cd742b0933 feat: 远程数据库增加默认数据 2023-07-24 14:58:55 +08:00
ssongliu 7f79f5f031 feat: 实现远程数据库改密、授权功能 2023-07-24 14:58:55 +08:00
ssongliu cb7351a9fb feat: 实现远程数据库增删改查 2023-07-24 14:58:55 +08:00
ssongliu bd5dc56b66 feat: Mysql 数据库增删、改密、改权限方法封装 2023-07-24 14:58:55 +08:00
zhengkunwang 34e8d88a53 feat: 优化网站 IPV6 设置 (#1732)
Refs https://github.com/1Panel-dev/1Panel/issues/1404
2023-07-22 14:04:17 +00:00
zhengkunwang c14e192bee feat: 网站增加重定向功能 (#1731)
Refs https://github.com/1Panel-dev/1Panel/issues/1654
Refs https://github.com/1Panel-dev/1Panel/issues/1387
Refs https://github.com/1Panel-dev/1Panel/issues/986
2023-07-22 14:02:21 +00:00
wangdan-fit2cloud d31eacd049 fix: 移动端文件列表UI异常问题修复 (#1730)
#### What this PR does / why we need it?

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-07-21 06:30:13 +00:00
wangdan-fit2cloud f556038425 Pr@dev@dan (#1721)
#### What this PR does / why we need it?

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-07-20 10:13:55 +00:00
dependabot[bot] 03ed4c8071 build(deps-dev): bump word-wrap from 1.2.3 to 1.2.4 in /frontend (#1726)
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-20 17:59:51 +08:00
zhengkunwang 86ba53d6ec style: 应用商店样式修改 (#1711) 2023-07-18 08:10:01 +00:00
ssongliu 0ca559f1e1 fix: 快速命令增加提示信息 (#1710) 2023-07-18 07:34:00 +00:00
ssongliu 1fd31fffd6 fix: 优化首页样式 (#1709) 2023-07-18 07:26:02 +00:00
wangdan-fit2cloud c62c19a49e fix: 修复首页高度对齐问题 (#1708)
#### What this PR does / why we need it?

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-07-18 06:28:35 +00:00
zhengkunwang aa8816f3ec feat: 网站已安装页面增加分页 (#1707) 2023-07-18 06:22:36 +00:00
zhengkunwang 920f1112fa feat: php 网站禁用函数支持输入下划线 (#1705) 2023-07-18 06:20:39 +00:00
ssongliu 4c2302dc94 fix: 国际化内容补全 (#1696) 2023-07-17 11:24:24 +00:00
ssongliu efebb26b5e fix: 解决容器创建端口选择 ipv6 校验问题 (#1695) 2023-07-17 08:38:21 +00:00
ssongliu 22fe2a6d51 fix: 本地连接失败时,打开添加主机抽屉 (#1691) 2023-07-17 08:36:24 +00:00
ssongliu e17b80cff4 fix: 解决部分接口命令注入问题 (#1690) 2023-07-17 08:34:29 +00:00
wangdan-fit2cloud 1d6f1b0ef3 fix: 优化首页对齐问题 (#1689)
#### What this PR does / why we need it?

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-07-17 08:32:34 +00:00
ssongliu dac0c81d96 fix: 解决主机密钥无法正常连接终端的问题 (#1675) 2023-07-14 14:40:12 +00:00
wangdan-fit2cloud e555dcb903 fix: 优化echart线状图表 (#1673)
#### What this PR does / why we need it?

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-07-14 14:38:16 +00:00
ssongliu 15c391e763 fix: 解决概览页左右高度不一致的问题 (#1671) 2023-07-14 08:22:11 +00:00
ssongliu 3b3584714c fix: 修改部分国际化内容 (#1669) 2023-07-14 07:18:11 +00:00
zhengkunwang 61a0244cfe fix: 修改部分国际化内容 (#1670) 2023-07-14 07:08:10 +00:00
wangdan-fit2cloud 73a61933c5 Pr@dev@dan (#1668)
#### What this PR does / why we need it?

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-07-14 06:34:10 +00:00
zhengkunwang 72c7407b0b style: 文件列表增加操作列宽度 (#1667) 2023-07-14 06:22:10 +00:00
zhengkunwang 6538282a7b fix: 解决网站停止之后再启动运行目录错误的问题 (#1666)
Refs https://github.com/1Panel-dev/1Panel/issues/1665
2023-07-14 06:20:19 +00:00
ssongliu 87326e3292 fix: 解决监控数据异常导致溢出的问题 2023-07-14 11:55:33 +08:00
zhengkunwang 6eadb116c2 feat: 修复当网站主域名与网站目录不一致时,定时任务切割网站日志失败的问题 (#1659) 2023-07-14 03:50:10 +00:00
zhengkunwang c9ffd2564e feat: 基本名称名称长度限制增加到 128 (#1658) 2023-07-14 03:48:11 +00:00
wangdan-fit2cloud cd6e5f7905 fix: 优化暗色背景下禁用按钮样式 (#1663)
#### What this PR does / why we need it?

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-07-14 03:38:10 +00:00
Mystery0 M b96e988f20 fix: 修复当网站主域名与网站目录不一致时,定时任务切割网站日志失败 (#1653)
* fix: 网站日志切割定时任务执行失败时打印失败原因,方便排查问题

* fix: 修复SiteDir与主域名不匹配时,切割网站日志失败
2023-07-13 22:25:58 +08:00
wzrove 30f7fa6afa fix: 优化监控页面echart资源释放 (#1651)
#### What this PR does / why we need it?

#1598 监控页面echart资源 组件卸载未释放

#### Summary of your change

优化监控页面echart资源释放

#### Please indicate you've done the following:

- [x] Made sure tests are passing and test coverage is added if needed.
- [x] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [x] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-07-13 12:47:18 +00:00
ssongliu 4605b19473 fix: 网站上传备份逻辑调整 (#1649) 2023-07-13 11:51:17 +00:00
zhengkunwang 87327053dc fix: 解决某些情况下应用状态异常的问题 (#1644) 2023-07-13 07:43:18 +00:00
ssongliu 5e7d5c0235 fix: 解决部分容器编辑无法正常打开的问题 (#1645) 2023-07-13 07:41:17 +00:00
zhengkunwang 1ccc56100c fix: 解决面板设置无法选择 https 证书的问题 (#1638) 2023-07-13 06:17:18 +00:00
ssongliu ef948bca76 fix: 解决概览页多磁盘刷新问题 (#1640) 2023-07-13 06:11:18 +00:00
ssongliu d01a964534 fix: 修改部分国际化问题 (#1639) 2023-07-13 06:09:23 +00:00
zhengkunwang 05004cb141 fix: 解决网站设置 HTTPS 证书显示错误的问题 (#1629)
Refs https://github.com/1Panel-dev/1Panel/issues/1626
2023-07-12 13:17:16 +00:00
zhengkunwang dfcef390d0 feat: 取消 openresty 安装时的端口开放提示 (#1627) 2023-07-12 13:13:16 +00:00
ssongliu ff3b41686c fix: 解决部分升级失败的问题 (#1624) 2023-07-12 13:11:20 +00:00
340 changed files with 20926 additions and 5005 deletions
+2
View File
@@ -33,3 +33,5 @@ install.sh
quick_start.sh
cmd/server/web/.DS_Store
cmd/server/.DS_Store
cmd/server/fileList.txt
.fileList.txt
-4
View File
@@ -29,10 +29,6 @@ build_backend_on_darwin:
cd $(SERVER_PATH) \
&& GOOS=linux GOARCH=amd64 $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
build_backend_on_archlinux:
cd $(SERVER_PATH) \
&& GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
build_all: build_frontend build_backend_on_linux
build_on_local: clean_assets build_frontend build_backend_on_darwin upx_bin
+3 -3
View File
@@ -1,12 +1,12 @@
[README_EN.md](README_EN.md)
<p align="center"><a href="https://1panel.cn"><img src="http://1panel.oss-cn-hangzhou.aliyuncs.com/img/1panel-logo.png" alt="1Panel" width="300" /></a></p>
<p align="center"><b>现代化、开源的 Linux 服务器运维管理面板</b></p>
<p align="center">
<a href="https://www.gnu.org/licenses/gpl-3.0.html"><img src="https://shields.io/github/license/1Panel-dev/1Panel" alt="License: GPL v3"></a>
<a href="https://www.gnu.org/licenses/gpl-3.0.html"><img src="https://shields.io/github/license/1Panel-dev/1Panel?color=%231890FF" alt="License: GPL v3"></a>
<a href="https://app.codacy.com/gh/1Panel-dev/1Panel?utm_source=github.com&utm_medium=referral&utm_content=1Panel-dev/1Panel&utm_campaign=Badge_Grade_Dashboard"><img src="https://app.codacy.com/project/badge/Grade/da67574fd82b473992781d1386b937ef" alt="Codacy"></a>
<a href="https://github.com/1Panel-dev/1Panel/releases"><img src="https://img.shields.io/github/v/release/1Panel-dev/1Panel" alt="GitHub release"></a>
<a href="https://github.com/1Panel-dev/1Panel"><img src="https://img.shields.io/github/stars/1Panel-dev/1Panel?color=%231890FF&style=flat-square" alt="Stars"></a>
<a href="https://app.fossa.com/projects/git%2Bgithub.com%2F1Panel-dev%2F1Panel?ref=badge_shield"><img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2F1Panel-dev%2F1Panel.svg?type=shield" alt="FOSSA Status"></a>
<a href="https://app.fossa.com/projects/git%2Bgithub.com%2F1Panel-dev%2F1Panel?ref=badge_shield"><img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2F1Panel-dev%2F1Panel.svg?type=shield" alt="FOSSA Status"></a><br>
[<a href="README_EN.md">English</a>] | [<a href="README_JP.md">日本語</a>]
</p>
------------------------------
+2 -2
View File
@@ -1,11 +1,11 @@
[中文 README.md](README.md)
<p align="center"><a href="https://1panel.cn"><img src="http://1panel.oss-cn-hangzhou.aliyuncs.com/img/1panel-logo.png" alt="1Panel" width="300" /></a></p>
<p align="center"><b>Modern and Open-Source Linux Server Operation and Management Panel</b></p>
<p align="center">
<a href="https://www.gnu.org/licenses/gpl-3.0.html"><img src="https://shields.io/github/license/1Panel-dev/1Panel" alt="License: GPL v3"></a>
<a href="https://app.codacy.com/gh/1Panel-dev/1Panel?utm_source=github.com&utm_medium=referral&utm_content=1Panel-dev/1Panel&utm_campaign=Badge_Grade_Dashboard"><img src="https://app.codacy.com/project/badge/Grade/da67574fd82b473992781d1386b937ef" alt="Codacy"></a>
<a href="https://github.com/1Panel-dev/1Panel/releases"><img src="https://img.shields.io/github/v/release/1Panel-dev/1Panel" alt="GitHub release"></a>
<a href="https://github.com/1Panel-dev/1Panel"><img src="https://img.shields.io/github/stars/1Panel-dev/1Panel?color=%231890FF&style=flat-square" alt="Stars"></a>
<a href="https://github.com/1Panel-dev/1Panel"><img src="https://img.shields.io/github/stars/1Panel-dev/1Panel?color=%231890FF&style=flat-square" alt="Stars"></a><br>
[<a href="README.md">中文</a>] | [<a href="README_JP.md">日本語</a>]
</p>
------------------------------
+72
View File
@@ -0,0 +1,72 @@
<p align="center"><a href="https://1panel.cn"><img src="http://1panel.oss-cn-hangzhou.aliyuncs.com/img/1panel-logo.png" alt="1Panel" width="300" /></a></p>
<p align="center"><b>モダンでオープンソースの Linux サーバー運用管理パネル</b></p>
<p align="center">
<a href="https://www.gnu.org/licenses/gpl-3.0.html"><img src="https://shields.io/github/license/1Panel-dev/1Panel" alt="License: GPL v3"></a>
<a href="https://app.codacy.com/gh/1Panel-dev/1Panel?utm_source=github.com&utm_medium=referral&utm_content=1Panel-dev/1Panel&utm_campaign=Badge_Grade_Dashboard"><img src="https://app.codacy.com/project/badge/Grade/da67574fd82b473992781d1386b937ef" alt="Codacy"></a>
<a href="https://github.com/1Panel-dev/1Panel/releases"><img src="https://img.shields.io/github/v/release/1Panel-dev/1Panel" alt="GitHub release"></a>
<a href="https://github.com/1Panel-dev/1Panel"><img src="https://img.shields.io/github/stars/1Panel-dev/1Panel?color=%231890FF&style=flat-square" alt="Stars"></a><br>
[<a href="README.md">中文</a>] | [<a href="README_EN.md">English</a>]
</p>
------------------------------
1Panel はモダンでオープンソースの Linux サーバー操作と管理パネルです:
- **素早いウェブサイト構築**: Wordpress および [Halo](https://github.com/halo-dev/halo/) と深く統合されており、ドメイン名のバインディング、SSL 証明書の設定など、ワンクリックで解決できます;
- **効率的なマネジメント**: アプリケーション管理、ホスト監視、ファイル管理、データベース管理、コンテナ管理など、web インターフェイスを通じて Linux サーバーを簡単に管理できます;
- **安全性と信頼性**: ファイアウォールとセキュリティ監査機能により、脆弱性の露出を最小限に抑えます;
- **ワンクリックバックアップ**: ワンクリックでのバックアップと復元をサポートし、バックアップデータはクラウドに保存され、失われることはありません。
## UI ディスプレイ
![UI Display](https://resource.fit2cloud.com/1panel/img/overview_en.png)
## クイックスタート
**オンラインでも**
- アドレス: <https://demo.1panel.cn/>
- ユーザネーム: demo
- パスワード: 1panel
**ワンクリックインストール**
ワンクリックで 1Panel をインストールするには、次のコマンドを実行します:
```sh
curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh && sudo bash quick_start.sh
```
**学習教材**
- [オンラインドキュメント](https://1panel.cn/docs/)
- [ビデオ教材](https://space.bilibili.com/510493147/channel/collectiondetail?sid=1199760)
## コミュニティ
ご質問やご提案がございましたら、GitHub Issue にご投稿いただくか、WeChat グループにご参加ください。
**WeChat グループ**
<img src="https://1panel.cn/img/wechat-group.jpg" width="156" height="156"/>
## セキュリティ情報
セキュリティ上の問題を発見された場合は、下記までご連絡ください:
- メール: support@fit2cloud.com
- 電話番号: 400-052-0755
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=1Panel-dev/1Panel&type=Date)](https://star-history.com/#1Panel-dev/1Panel&Date)
## License
Copyright (c) 2014-2023 [FIT2CLOUD 飞致云](https://fit2cloud.com/), All rights reserved.
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
<https://www.gnu.org/licenses/gpl-3.0.html>
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+45 -26
View File
@@ -1,7 +1,6 @@
package v1
import (
"errors"
"reflect"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
@@ -14,8 +13,8 @@ import (
)
// @Tags App
// @Summary List app installed
// @Description 获取已安装应用列表
// @Summary Page app installed
// @Description 分页获取已安装应用列表
// @Accept json
// @Param request body request.AppInstalledSearch true "request"
// @Success 200
@@ -47,21 +46,41 @@ func (b *BaseApi) SearchAppInstalled(c *gin.Context) {
}
}
// @Tags App
// @Summary List app installed
// @Description 获取已安装应用列表
// @Accept json
// @Success 200 array dto.AppInstallInfo
// @Security ApiKeyAuth
// @Router /apps/installed/list [get]
func (b *BaseApi) ListAppInstalled(c *gin.Context) {
list, err := appInstallService.GetInstallList()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, list)
}
// @Tags App
// @Summary Check app installed
// @Description 检查应用安装情况
// @Accept json
// @Param key path string true "request"
// @Param request body request.AppInstalledInfo true "request"
// @Success 200 {object} response.AppInstalledCheck
// @Security ApiKeyAuth
// @Router /apps/installed/check/:key [get]
// @Router /apps/installed/check [post]
func (b *BaseApi) CheckAppInstalled(c *gin.Context) {
key, ok := c.Params.Get("key")
if !ok {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error key in path"))
var req request.AppInstalledInfo
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
checkData, err := appInstallService.CheckExist(key)
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
checkData, err := appInstallService.CheckExist(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -73,17 +92,17 @@ func (b *BaseApi) CheckAppInstalled(c *gin.Context) {
// @Summary Search app port by key
// @Description 获取应用端口
// @Accept json
// @Param key path string true "request"
// @Param request body dto.OperationWithNameAndType true "request"
// @Success 200 {integer} port
// @Security ApiKeyAuth
// @Router /apps/installed/loadport/:key [get]
// @Router /apps/installed/loadport [post]
func (b *BaseApi) LoadPort(c *gin.Context) {
key, ok := c.Params.Get("key")
if !ok {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error key in path"))
var req dto.OperationWithNameAndType
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
port, err := appInstallService.LoadPort(key)
port, err := appInstallService.LoadPort(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -95,17 +114,17 @@ func (b *BaseApi) LoadPort(c *gin.Context) {
// @Summary Search app password by key
// @Description 获取应用连接信息
// @Accept json
// @Param key path string true "request"
// @Param request body dto.OperationWithNameAndType true "request"
// @Success 200 {string} response.DatabaseConn
// @Security ApiKeyAuth
// @Router /apps/installed/conninfo/:key [get]
func (b *BaseApi) LoadConnInfo(c *gin.Context) {
key, ok := c.Params.Get("key")
if !ok {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error key in path"))
var req dto.OperationWithNameAndType
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
conn, err := appInstallService.LoadConnInfo(key)
conn, err := appInstallService.LoadConnInfo(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -243,17 +262,17 @@ func (b *BaseApi) ChangeAppPort(c *gin.Context) {
// @Summary Search default config by key
// @Description 通过 key 获取应用默认配置
// @Accept json
// @Param key path string true "request"
// @Param request body dto.OperationWithNameAndType true "request"
// @Success 200 {string} content
// @Security ApiKeyAuth
// @Router /apps/installed/conf/:key [get]
// @Router /apps/installed/conf [post]
func (b *BaseApi) GetDefaultConfig(c *gin.Context) {
key := c.Param("key")
if key == "" {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil)
var req dto.OperationWithNameAndType
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
content, err := appInstallService.GetDefaultConfigByKey(key)
content, err := appInstallService.GetDefaultConfigByKey(req.Type, req.Name)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
+30 -2
View File
@@ -1,6 +1,8 @@
package v1
import (
"encoding/base64"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
@@ -20,6 +22,7 @@ type BaseApi struct{}
// @Param request body dto.Login true "request"
// @Success 200 {object} dto.UserLoginInfo
// @Router /auth/login [post]
// @Header 200 {string} EntranceCode "安全入口"
func (b *BaseApi) Login(c *gin.Context) {
var req dto.Login
if err := c.ShouldBindJSON(&req); err != nil {
@@ -32,8 +35,13 @@ func (b *BaseApi) Login(c *gin.Context) {
return
}
}
entranceItem := c.Request.Header.Get("EntranceCode")
var entrance []byte
if len(entranceItem) != 0 {
entrance, _ = base64.StdEncoding.DecodeString(entranceItem)
}
user, err := authService.Login(c, req)
user, err := authService.Login(c, req, string(entrance))
go saveLoginLogs(c, err)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
@@ -49,6 +57,7 @@ func (b *BaseApi) Login(c *gin.Context) {
// @Param request body dto.MFALogin true "request"
// @Success 200 {object} dto.UserLoginInfo
// @Router /auth/mfalogin [post]
// @Header 200 {string} EntranceCode "安全入口"
func (b *BaseApi) MFALogin(c *gin.Context) {
var req dto.MFALogin
if err := c.ShouldBindJSON(&req); err != nil {
@@ -59,8 +68,13 @@ func (b *BaseApi) MFALogin(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
entranceItem := c.Request.Header.Get("EntranceCode")
var entrance []byte
if len(entranceItem) != 0 {
entrance, _ = base64.StdEncoding.DecodeString(entranceItem)
}
user, err := authService.MFALogin(c, req)
user, err := authService.MFALogin(c, req, string(entrance))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -120,6 +134,20 @@ func (b *BaseApi) CheckIsDemo(c *gin.Context) {
helper.SuccessWithData(c, global.CONF.System.IsDemo)
}
// @Tags Auth
// @Summary Load System Language
// @Description 获取系统语言设置
// @Success 200
// @Router /auth/language [get]
func (b *BaseApi) GetLanguage(c *gin.Context) {
settingInfo, err := settingService.GetSettingInfo()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, settingInfo.Language)
}
func saveLoginLogs(c *gin.Context, err error) {
var logs model.LoginLog
if err != nil {
+3 -3
View File
@@ -335,7 +335,7 @@ func (b *BaseApi) Backup(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
case "mysql":
case "mysql", "mariadb":
if err := backupService.MysqlBackup(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -383,7 +383,7 @@ func (b *BaseApi) Recover(c *gin.Context) {
req.File = downloadPath
}
switch req.Type {
case "mysql":
case "mysql", "mariadb":
if err := backupService.MysqlRecover(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -428,7 +428,7 @@ func (b *BaseApi) RecoverByUpload(c *gin.Context) {
}
switch req.Type {
case "mysql":
case "mysql", "mariadb":
if err := backupService.MysqlRecoverByUpload(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
+18
View File
@@ -355,6 +355,24 @@ func (b *BaseApi) CleanContainerLog(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags Container
// @Summary Load container log
// @Description 获取容器操作日志
// @Accept json
// @Param request body dto.OperationWithNameAndType true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /containers/load/log [post]
func (b *BaseApi) LoadContainerLog(c *gin.Context) {
var req dto.OperationWithNameAndType
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
content := containerService.LoadContainerLogs(req)
helper.SuccessWithData(c, content)
}
// @Tags Container
// @Summary Operate Container
// @Description 容器操作
+24 -1
View File
@@ -94,6 +94,28 @@ func (b *BaseApi) SearchJobRecords(c *gin.Context) {
})
}
// @Tags Cronjob
// @Summary Load Cronjob record log
// @Description 获取计划任务记录日志
// @Accept json
// @Param request body dto.OperateByID true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /cronjob/record/log [post]
func (b *BaseApi) LoadRecordLog(c *gin.Context) {
var req dto.OperateByID
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
content, err := cronjobService.LoadRecordLog(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, content)
}
// @Tags Cronjob
// @Summary Clean job records
// @Description 清空计划任务记录
@@ -224,7 +246,8 @@ func (b *BaseApi) TargetDownload(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, filePath)
c.File(filePath)
}
// @Tags Cronjob
+180
View File
@@ -0,0 +1,180 @@
package v1
import (
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/gin-gonic/gin"
)
// @Tags Database
// @Summary Create database
// @Description 创建远程数据库
// @Accept json
// @Param request body dto.DatabaseCreate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/db [post]
// @x-panel-log {"bodyKeys":["name", "type"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建远程数据库 [name][type]","formatEN":"create database [name][type]"}
func (b *BaseApi) CreateDatabase(c *gin.Context) {
var req dto.DatabaseCreate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := databaseService.Create(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Database
// @Summary Check database
// @Description 检测远程数据库连接性
// @Accept json
// @Param request body dto.DatabaseCreate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/db/check [post]
// @x-panel-log {"bodyKeys":["name", "type"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"检测远程数据库 [name][type] 连接性","formatEN":"check if database [name][type] is connectable"}
func (b *BaseApi) CheckDatabase(c *gin.Context) {
var req dto.DatabaseCreate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
helper.SuccessWithData(c, databaseService.CheckDatabase(req))
}
// @Tags Database
// @Summary Page databases
// @Description 获取远程数据库列表分页
// @Accept json
// @Param request body dto.DatabaseSearch true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Router /databases/db/search [post]
func (b *BaseApi) SearchDatabase(c *gin.Context) {
var req dto.DatabaseSearch
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
total, list, err := databaseService.SearchWithPage(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}
// @Tags Database
// @Summary List databases
// @Description 获取远程数据库列表
// @Success 200 {array} dto.DatabaseOption
// @Security ApiKeyAuth
// @Router /databases/db/list/:type [get]
func (b *BaseApi) ListDatabase(c *gin.Context) {
dbType, err := helper.GetStrParamByKey(c, "type")
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
list, err := databaseService.List(dbType)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, list)
}
// @Tags Database
// @Summary Get databases
// @Description 获取远程数据库
// @Success 200 {object} dto.DatabaseInfo
// @Security ApiKeyAuth
// @Router /databases/db/:name [get]
func (b *BaseApi) GetDatabase(c *gin.Context) {
name, err := helper.GetStrParamByKey(c, "name")
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
data, err := databaseService.Get(name)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags Database
// @Summary Delete database
// @Description 删除远程数据库
// @Accept json
// @Param request body dto.OperateByID true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/db/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"ids","isList":true,"db":"databases","output_column":"name","output_value":"names"}],"formatZH":"删除远程数据库 [names]","formatEN":"delete database [names]"}
func (b *BaseApi) DeleteDatabase(c *gin.Context) {
var req dto.OperateByID
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := databaseService.Delete(req.ID); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Database
// @Summary Update database
// @Description 更新远程数据库
// @Accept json
// @Param request body dto.DatabaseUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/db/update [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新远程数据库 [name]","formatEN":"update database [name]"}
func (b *BaseApi) UpdateDatabase(c *gin.Context) {
var req dto.DatabaseUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := databaseService.Update(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
+91 -16
View File
@@ -143,7 +143,7 @@ func (b *BaseApi) ChangeMysqlAccess(c *gin.Context) {
// @Router /databases/variables/update [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"调整 mysql 数据库性能参数","formatEN":"adjust mysql database performance parameters"}
func (b *BaseApi) UpdateMysqlVariables(c *gin.Context) {
var req []dto.MysqlVariablesUpdate
var req dto.MysqlVariablesUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
@@ -188,12 +188,12 @@ func (b *BaseApi) UpdateMysqlConfByFile(c *gin.Context) {
// @Summary Page mysql databases
// @Description 获取 mysql 数据库列表分页
// @Accept json
// @Param request body dto.SearchWithPage true "request"
// @Param request body dto.MysqlDBSearch true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Router /databases/search [post]
func (b *BaseApi) SearchMysql(c *gin.Context) {
var req dto.SearchWithPage
var req dto.MysqlDBSearch
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
@@ -216,11 +216,11 @@ func (b *BaseApi) SearchMysql(c *gin.Context) {
// @Description 获取 mysql 数据库列表
// @Accept json
// @Param request body dto.PageInfo true "request"
// @Success 200 {array} string
// @Success 200 {array} dto.MysqlOption
// @Security ApiKeyAuth
// @Router /databases/options [get]
func (b *BaseApi) ListDBName(c *gin.Context) {
list, err := mysqlService.ListDBName()
list, err := mysqlService.ListDBOption()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -229,16 +229,41 @@ func (b *BaseApi) ListDBName(c *gin.Context) {
helper.SuccessWithData(c, list)
}
// @Tags Database Mysql
// @Summary Load mysql database from remote
// @Description 从服务器获取
// @Accept json
// @Param request body dto.MysqlLoadDB true "request"
// @Security ApiKeyAuth
// @Router /databases/load [post]
func (b *BaseApi) LoadDBFromRemote(c *gin.Context) {
var req dto.MysqlLoadDB
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := mysqlService.LoadFromRemote(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Database Mysql
// @Summary Check before delete mysql database
// @Description Mysql 数据库删除前检查
// @Accept json
// @Param request body dto.OperateByID true "request"
// @Param request body dto.MysqlDBDeleteCheck true "request"
// @Success 200 {array} string
// @Security ApiKeyAuth
// @Router /databases/del/check [post]
func (b *BaseApi) DeleteCheckMysql(c *gin.Context) {
var req dto.OperateByID
var req dto.MysqlDBDeleteCheck
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
@@ -248,7 +273,7 @@ func (b *BaseApi) DeleteCheckMysql(c *gin.Context) {
return
}
apps, err := mysqlService.DeleteCheck(req.ID)
apps, err := mysqlService.DeleteCheck(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -289,11 +314,18 @@ func (b *BaseApi) DeleteMysql(c *gin.Context) {
// @Tags Database Mysql
// @Summary Load mysql base info
// @Description 获取 mysql 基础信息
// @Accept json
// @Param request body dto.OperationWithNameAndType true "request"
// @Success 200 {object} dto.DBBaseInfo
// @Security ApiKeyAuth
// @Router /databases/baseinfo [get]
// @Router /databases/baseinfo [post]
func (b *BaseApi) LoadBaseinfo(c *gin.Context) {
data, err := mysqlService.LoadBaseInfo()
var req dto.OperationWithNameAndType
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
data, err := mysqlService.LoadBaseInfo(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -302,14 +334,43 @@ func (b *BaseApi) LoadBaseinfo(c *gin.Context) {
helper.SuccessWithData(c, data)
}
// @Tags Database
// @Summary Load Database file
// @Description 获取数据库文件
// @Accept json
// @Param request body dto.OperationWithNameAndType true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/load/file [post]
func (b *BaseApi) LoadDatabaseFile(c *gin.Context) {
var req dto.OperationWithNameAndType
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
content, err := mysqlService.LoadDatabaseFile(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, content)
}
// @Tags Database Mysql
// @Summary Load mysql remote access
// @Description 获取 mysql 远程访问权限
// @Accept json
// @Param request body dto.OperationWithNameAndType true "request"
// @Success 200 {boolean} isRemote
// @Security ApiKeyAuth
// @Router /databases/remote [get]
// @Router /databases/remote [post]
func (b *BaseApi) LoadRemoteAccess(c *gin.Context) {
isRemote, err := mysqlService.LoadRemoteAccess()
var req dto.OperationWithNameAndType
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
isRemote, err := mysqlService.LoadRemoteAccess(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -321,11 +382,18 @@ func (b *BaseApi) LoadRemoteAccess(c *gin.Context) {
// @Tags Database Mysql
// @Summary Load mysql status info
// @Description 获取 mysql 状态信息
// @Accept json
// @Param request body dto.OperationWithNameAndType true "request"
// @Success 200 {object} dto.MysqlStatus
// @Security ApiKeyAuth
// @Router /databases/status [get]
// @Router /databases/status [post]
func (b *BaseApi) LoadStatus(c *gin.Context) {
data, err := mysqlService.LoadStatus()
var req dto.OperationWithNameAndType
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
data, err := mysqlService.LoadStatus(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@@ -337,11 +405,18 @@ func (b *BaseApi) LoadStatus(c *gin.Context) {
// @Tags Database Mysql
// @Summary Load mysql variables info
// @Description 获取 mysql 性能参数信息
// @Accept json
// @Param request body dto.OperationWithNameAndType true "request"
// @Success 200 {object} dto.MysqlVariables
// @Security ApiKeyAuth
// @Router /databases/variables [get]
// @Router /databases/variables [post]
func (b *BaseApi) LoadVariables(c *gin.Context) {
data, err := mysqlService.LoadVariables()
var req dto.OperationWithNameAndType
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
data, err := mysqlService.LoadVariables(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
+2 -2
View File
@@ -92,13 +92,13 @@ func (b *BaseApi) UpdateRedisConf(c *gin.Context) {
// @Summary Change redis password
// @Description 更新 redis 密码
// @Accept json
// @Param request body dto.ChangeDBInfo true "request"
// @Param request body dto.ChangeRedisPass true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/redis/password [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"修改 redis 数据库密码","formatEN":"change the password of the redis database"}
func (b *BaseApi) ChangeRedisPassword(c *gin.Context) {
var req dto.ChangeDBInfo
var req dto.ChangeRedisPass
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
+5 -2
View File
@@ -21,8 +21,9 @@ var (
imageService = service.NewIImageService()
dockerService = service.NewIDockerService()
mysqlService = service.NewIMysqlService()
redisService = service.NewIRedisService()
mysqlService = service.NewIMysqlService()
databaseService = service.NewIDatabaseService()
redisService = service.NewIRedisService()
cronjobService = service.NewICronjobService()
@@ -50,4 +51,6 @@ var (
runtimeService = service.NewRuntimeService()
processService = service.NewIProcessService()
hostToolService = service.NewIHostToolService()
)
-49
View File
@@ -544,28 +544,6 @@ func (b *BaseApi) DownloadChunkFiles(c *gin.Context) {
}
}
// @Tags File
// @Summary Download file with path
// @Description 下载指定文件
// @Accept json
// @Param request body dto.FilePath true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /files/download/bypath [post]
// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"下载文件 [path]","formatEN":"Download file [path]"}
func (b *BaseApi) DownloadFile(c *gin.Context) {
var req dto.FilePath
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
c.File(req.Path)
}
// @Tags File
// @Summary Load file size
// @Description 获取文件夹大小
@@ -589,33 +567,6 @@ func (b *BaseApi) Size(c *gin.Context) {
helper.SuccessWithData(c, res)
}
// @Tags File
// @Summary Read file
// @Description 读取文件
// @Accept json
// @Param request body dto.FilePath true "request"
// @Success 200 {string} content
// @Security ApiKeyAuth
// @Router /files/loadfile [post]
func (b *BaseApi) LoadFromFile(c *gin.Context) {
var req dto.FilePath
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
content, err := os.ReadFile(req.Path)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, string(content))
}
func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int) error {
if _, err := os.Stat(path.Dir(dstDir)); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(path.Dir(dstDir), os.ModePerm); err != nil {
+29
View File
@@ -38,6 +38,10 @@ func (b *BaseApi) SearchFirewallRule(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
total, list, err := firewallService.SearchWithPage(req)
if err != nil {
@@ -156,6 +160,31 @@ func (b *BaseApi) BatchOperateRule(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags Firewall
// @Summary Update rule description
// @Description 更新防火墙描述
// @Accept json
// @Param request body dto.UpdateFirewallDescription true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/firewall/update/description [post]
func (b *BaseApi) UpdateFirewallDescription(c *gin.Context) {
var req dto.UpdateFirewallDescription
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := firewallService.UpdateDescription(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Firewall
// @Summary Create group
// @Description 更新端口防火墙规则
+9 -59
View File
@@ -1,8 +1,6 @@
package v1
import (
"encoding/base64"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant"
@@ -30,44 +28,6 @@ func (b *BaseApi) CreateHost(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if req.AuthMode == "password" && len(req.Password) != 0 {
password, err := base64.StdEncoding.DecodeString(req.Password)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
passwordItem, err := encrypt.StringEncrypt(string(password))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.Password = passwordItem
req.PrivateKey = ""
req.PassPhrase = ""
}
if req.AuthMode == "key" && len(req.PrivateKey) != 0 {
privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
keyItem, err := encrypt.StringEncrypt(string(privateKey))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.Password = keyItem
if len(req.PassPhrase) != 0 {
pass, err := encrypt.StringEncrypt(req.PassPhrase)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.PassPhrase = pass
}
req.Password = ""
}
host, err := hostService.Create(req)
if err != nil {
@@ -216,40 +176,30 @@ func (b *BaseApi) UpdateHost(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if req.AuthMode == "password" && len(req.Password) != 0 {
password, err := base64.StdEncoding.DecodeString(req.Password)
var err error
if len(req.Password) != 0 && req.AuthMode == "password" {
req.Password, err = hostService.EncryptHost(req.Password)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
passwordItem, err := encrypt.StringEncrypt(string(password))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.Password = passwordItem
req.PrivateKey = ""
req.PassPhrase = ""
}
if req.AuthMode == "key" && len(req.PrivateKey) != 0 {
privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey)
if len(req.PrivateKey) != 0 && req.AuthMode == "key" {
req.PrivateKey, err = hostService.EncryptHost(req.PrivateKey)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
keyItem, err := encrypt.StringEncrypt(string(privateKey))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.PrivateKey = keyItem
if len(req.PassPhrase) != 0 {
pass, err := encrypt.StringEncrypt(req.PassPhrase)
req.PassPhrase, err = encrypt.StringEncrypt(req.PassPhrase)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.PassPhrase = pass
}
req.Password = ""
}
upMap := make(map[string]interface{})
+213
View File
@@ -0,0 +1,213 @@
package v1
import (
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/gin-gonic/gin"
)
// @Tags Host tool
// @Summary Get tool
// @Description 获取主机工具状态
// @Accept json
// @Param request body request.HostToolReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /host/tool [post]
func (b *BaseApi) GetToolStatus(c *gin.Context) {
var req request.HostToolReq
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
config, err := hostToolService.GetToolStatus(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, config)
}
// @Tags Host tool
// @Summary Create Host tool Config
// @Description 创建主机工具配置
// @Accept json
// @Param request body request.HostToolCreate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /host/tool/create [post]
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建 [type] 配置","formatEN":"create [type] config"}
func (b *BaseApi) InitToolConfig(c *gin.Context) {
var req request.HostToolCreate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := hostToolService.CreateToolConfig(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Host tool
// @Summary Operate tool
// @Description 操作主机工具
// @Accept json
// @Param request body request.HostToolReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /host/tool/operate [post]
// @x-panel-log {"bodyKeys":["operate","type"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"[operate] [type] ","formatEN":"[operate] [type]"}
func (b *BaseApi) OperateTool(c *gin.Context) {
var req request.HostToolReq
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
err := hostToolService.OperateTool(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Host tool
// @Summary Get tool config
// @Description 操作主机工具配置文件
// @Accept json
// @Param request body request.HostToolConfig true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /host/tool/config [post]
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"[operate] 主机工具配置文件 ","formatEN":"[operate] tool config"}
func (b *BaseApi) OperateToolConfig(c *gin.Context) {
var req request.HostToolConfig
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
config, err := hostToolService.OperateToolConfig(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, config)
}
// @Tags Host tool
// @Summary Get tool
// @Description 获取主机工具日志
// @Accept json
// @Param request body request.HostToolLogReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /host/tool/log [post]
func (b *BaseApi) GetToolLog(c *gin.Context) {
var req request.HostToolLogReq
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
logContent, err := hostToolService.GetToolLog(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, logContent)
}
// @Tags Host tool
// @Summary Create Supervisor process
// @Description 操作守护进程
// @Accept json
// @Param request body request.SupervisorProcessConfig true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /host/tool/supervisor/process [post]
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"[operate] 守护进程 ","formatEN":"[operate] process"}
func (b *BaseApi) OperateProcess(c *gin.Context) {
var req request.SupervisorProcessConfig
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
err := hostToolService.OperateSupervisorProcess(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Host tool
// @Summary Get Supervisor process config
// @Description 获取 Supervisor 进程配置
// @Accept json
// @Success 200
// @Security ApiKeyAuth
// @Router /host/tool/supervisor/process [get]
func (b *BaseApi) GetProcess(c *gin.Context) {
configs, err := hostToolService.GetSupervisorProcessConfig()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, configs)
}
// @Tags Host tool
// @Summary Get Supervisor process config
// @Description 操作 Supervisor 进程文件
// @Accept json
// @Param request body request.SupervisorProcessFileReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /host/tool/supervisor/process/file [post]
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"[operate] Supervisor 进程文件 ","formatEN":"[operate] Supervisor Process Config file"}
func (b *BaseApi) GetProcessFile(c *gin.Context) {
var req request.SupervisorProcessFileReq
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
content, err := hostToolService.OperateSupervisorProcessFile(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, content)
}
+16
View File
@@ -89,3 +89,19 @@ func (b *BaseApi) CleanLogs(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags Logs
// @Summary Load system logs
// @Description 获取系统日志
// @Success 200
// @Security ApiKeyAuth
// @Router /logs/system [get]
func (b *BaseApi) GetSystemLogs(c *gin.Context) {
data, err := logService.LoadSystemLog()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
+18
View File
@@ -3,6 +3,8 @@ package v1
import (
"errors"
"fmt"
"os"
"path"
"strconv"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
@@ -134,6 +136,22 @@ func (b *BaseApi) LoadFromCert(c *gin.Context) {
helper.SuccessWithData(c, info)
}
// @Tags System Setting
// @Summary Download system cert
// @Description 下载证书
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/ssl/download [post]
func (b *BaseApi) DownloadSSL(c *gin.Context) {
pathItem := path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt")
if _, err := os.Stat(pathItem); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
c.File(pathItem)
}
// @Tags System Setting
// @Summary Update system port
// @Description 更新系统端口
+26
View File
@@ -60,6 +60,32 @@ func (b *BaseApi) ImportSnapshot(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags System Setting
// @Summary Load Snapshot status
// @Description 获取快照状态
// @Accept json
// @Param request body dto.OperateByID true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/snapshot/status [post]
func (b *BaseApi) LoadSnapShotStatus(c *gin.Context) {
var req dto.OperateByID
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
data, err := snapshotService.LoadSnapShotStatus(req.ID)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags System Setting
// @Summary Update snapshot description
// @Description 更新快照描述信息
+15
View File
@@ -183,3 +183,18 @@ func (b *BaseApi) LoadSSHLogs(c *gin.Context) {
}
helper.SuccessWithData(c, data)
}
// @Tags SSH
// @Summary Load host ssh conf
// @Description 获取 ssh 配置文件
// @Success 200
// @Security ApiKeyAuth
// @Router /host/ssh/conf [get]
func (b *BaseApi) LoadSSHConf(c *gin.Context) {
data, err := sshService.LoadSSHConf()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
+1 -6
View File
@@ -55,12 +55,7 @@ func (b *BaseApi) WsSsh(c *gin.Context) {
return
}
defer client.Close()
ssConn, err := connInfo.NewSshConn(cols, rows)
if wshandleError(wsConn, err) {
return
}
defer ssConn.Close()
sws, err := terminal.NewLogicSshWsSession(cols, rows, true, connInfo.Client, wsConn)
if wshandleError(wsConn, err) {
return
+133
View File
@@ -428,6 +428,28 @@ func (b *BaseApi) UpdateWebsiteWafConfig(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags Website WAF
// @Summary Update website waf file
// @Description 更新 网站 waf 配置文件
// @Accept json
// @Param request body request.WebsiteWafUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/waf/file/update [post]
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"WAF 配置文件修改 [domain]","formatEN":"WAF conf file update [domain]"}
func (b *BaseApi) UpdateWebsiteWafFile(c *gin.Context) {
var req request.WebsiteWafFileUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := websiteService.UpdateWafFile(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Website Nginx
// @Summary Update website nginx conf
// @Description 更新 网站 nginx 配置
@@ -561,6 +583,28 @@ func (b *BaseApi) UpdatePHPFile(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags Website PHP
// @Summary Update php version
// @Description 变更 php 版本
// @Accept json
// @Param request body request.WebsitePHPVersionReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/php/version [post]
// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"php 版本变更 [domain]","formatEN":"php version update [domain]"}
func (b *BaseApi) ChangePHPVersion(c *gin.Context) {
var req request.WebsitePHPVersionReq
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := websiteService.ChangePHPVersion(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Website
// @Summary Get rewrite conf
// @Description 获取伪静态配置
@@ -801,3 +845,92 @@ func (b *BaseApi) UpdateAntiLeech(c *gin.Context) {
}
helper.SuccessWithOutData(c)
}
// @Tags Website
// @Summary Update redirect conf
// @Description 修改重定向配置
// @Accept json
// @Param request body request.NginxRedirectReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/redirect/update [post]
// @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"websiteID","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"修改网站 [domain] 重定向理配置 ","formatEN":"Update domain [domain] redirect config"}
func (b *BaseApi) UpdateRedirectConfig(c *gin.Context) {
var req request.NginxRedirectReq
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
err := websiteService.OperateRedirect(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Website
// @Summary Get redirect conf
// @Description 获取重定向配置
// @Accept json
// @Param request body request.WebsiteProxyReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/redirect [post]
func (b *BaseApi) GetRedirectConfig(c *gin.Context) {
var req request.WebsiteRedirectReq
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
res, err := websiteService.GetRedirect(req.WebsiteID)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, res)
}
// @Tags Website
// @Summary Update redirect file
// @Description 更新重定向文件
// @Accept json
// @Param request body request.NginxRedirectUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/redirect/file [post]
// @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFuntions":[{"input_column":"id","input_value":"websiteID","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新重定向文件 [domain]","formatEN":"Nginx conf redirect file update [domain]"}
func (b *BaseApi) UpdateRedirectConfigFile(c *gin.Context) {
var req request.NginxRedirectUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := websiteService.UpdateRedirectFile(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Website
// @Summary Get website dir
// @Description 获取网站目录配置
// @Accept json
// @Param request body request.WebsiteCommonReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/dir [post]
func (b *BaseApi) GetDirConfig(c *gin.Context) {
var req request.WebsiteCommonReq
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
res, err := websiteService.LoadWebsiteDirConfig(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, res)
}
+12 -1
View File
@@ -15,6 +15,10 @@ type AuthParam struct {
RootPassword string `json:"PANEL_DB_ROOT_PASSWORD"`
}
type RedisAuthParam struct {
RootPassword string `json:"PANEL_REDIS_ROOT_PASSWORD"`
}
type ContainerExec struct {
ContainerName string `json:"containerName"`
DbParam AppDatabase `json:"dbParam"`
@@ -63,7 +67,8 @@ type LocalAppInstallDefine struct {
}
type ExtraProperties struct {
Tags []Tag `json:"tags"`
Tags []Tag `json:"tags"`
Version string `json:"version"`
}
type AppProperty struct {
@@ -128,3 +133,9 @@ var AppToolMap = map[string]string{
"mysql": "phpmyadmin",
"redis": "redis-commander",
}
type AppInstallInfo struct {
ID uint `json:"id"`
Key string `json:"key"`
Name string `json:"name"`
}
+1
View File
@@ -24,6 +24,7 @@ type Login struct {
Captcha string `json:"captcha"`
CaptchaID string `json:"captchaID"`
AuthMethod string `json:"authMethod"`
Language string `json:"language"`
}
type MFALogin struct {
+2 -9
View File
@@ -21,25 +21,18 @@ type BackupInfo struct {
Vars string `json:"vars"`
}
type BackupSearch struct {
PageInfo
Type string `json:"type" validate:"required,oneof=website mysql"`
Name string `json:"name" validate:"required"`
DetailName string `json:"detailName"`
}
type BackupSearchFile struct {
Type string `json:"type" validate:"required"`
}
type CommonBackup struct {
Type string `json:"type" validate:"required,oneof=app mysql redis website"`
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website"`
Name string `json:"name"`
DetailName string `json:"detailName"`
}
type CommonRecover struct {
Source string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL COS KODO OneDrive"`
Type string `json:"type" validate:"required,oneof=app mysql redis website"`
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website"`
Name string `json:"name"`
DetailName string `json:"detailName"`
File string `json:"file"`
+3 -1
View File
@@ -24,7 +24,8 @@ type ContainerInfo struct {
State string `json:"state"`
RunTime string `json:"runTime"`
Ports []string `json:"ports"`
Network []string `json:"network"`
Ports []string `json:"ports"`
IsFromApp bool `json:"isFromApp"`
IsFromCompose bool `json:"isFromCompose"`
@@ -44,6 +45,7 @@ type ContainerOperate struct {
PublishAllPorts bool `json:"publishAllPorts"`
ExposedPorts []PortHelper `json:"exposedPorts"`
Cmd []string `json:"cmd"`
Entrypoint []string `json:"entrypoint"`
CPUShares int64 `json:"cpuShares"`
NanoCPUs float64 `json:"nanoCPUs"`
Memory float64 `json:"memory"`
+3
View File
@@ -14,6 +14,7 @@ type CronjobCreate struct {
Script string `json:"script"`
ContainerName string `json:"containerName"`
AppID string `json:"appID"`
Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"`
DBName string `json:"dbName"`
@@ -36,6 +37,7 @@ type CronjobUpdate struct {
Script string `json:"script"`
ContainerName string `json:"containerName"`
AppID string `json:"appID"`
Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"`
DBName string `json:"dbName"`
@@ -79,6 +81,7 @@ type CronjobInfo struct {
Script string `json:"script"`
ContainerName string `json:"containerName"`
AppID string `json:"appID"`
Website string `json:"website"`
ExclusionRules string `json:"exclusionRules"`
DBName string `json:"dbName"`
+110 -30
View File
@@ -2,10 +2,20 @@ package dto
import "time"
type MysqlDBSearch struct {
PageInfo
Info string `json:"info"`
Database string `json:"database" validate:"required"`
OrderBy string `json:"orderBy"`
Order string `json:"order"`
}
type MysqlDBInfo struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"`
Name string `json:"name"`
From string `json:"from"`
MysqlName string `json:"mysqlName"`
Format string `json:"format"`
Username string `json:"username"`
Password string `json:"password"`
@@ -14,8 +24,18 @@ type MysqlDBInfo struct {
Description string `json:"description"`
}
type MysqlOption struct {
ID uint `json:"id"`
From string `json:"from"`
Type string `json:"type"`
Database string `json:"database"`
Name string `json:"name"`
}
type MysqlDBCreate struct {
Name string `json:"name" validate:"required"`
From string `json:"from" validate:"required,oneof=local remote"`
Database string `json:"database" validate:"required"`
Format string `json:"format" validate:"required,oneof=utf8mb4 utf8 gbk big5"`
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
@@ -23,10 +43,24 @@ type MysqlDBCreate struct {
Description string `json:"description"`
}
type MysqlLoadDB struct {
From string `json:"from" validate:"required,oneof=local remote"`
Type string `json:"type" validate:"required,oneof=mysql mariadb"`
Database string `json:"database" validate:"required"`
}
type MysqlDBDeleteCheck struct {
ID uint `json:"id" validate:"required"`
Type string `json:"type" validate:"required,oneof=mysql mariadb"`
Database string `json:"database" validate:"required"`
}
type MysqlDBDelete struct {
ID uint `json:"id" validate:"required"`
ForceDelete bool `json:"forceDelete"`
DeleteBackup bool `json:"deleteBackup"`
ID uint `json:"id" validate:"required"`
Type string `json:"type" validate:"required,oneof=mysql mariadb"`
Database string `json:"database" validate:"required"`
ForceDelete bool `json:"forceDelete"`
DeleteBackup bool `json:"deleteBackup"`
}
type MysqlStatus struct {
@@ -90,17 +124,27 @@ type MysqlVariables struct {
}
type MysqlVariablesUpdate struct {
Type string `json:"type" validate:"required,oneof=mysql mariadb"`
Database string `json:"database" validate:"required"`
Variables []MysqlVariablesUpdateHelper `json:"variables"`
}
type MysqlVariablesUpdateHelper struct {
Param string `json:"param"`
Value interface{} `json:"value"`
}
type MysqlConfUpdateByFile struct {
MysqlName string `json:"mysqlName" validate:"required"`
File string `json:"file"`
Type string `json:"type" validate:"required,oneof=mysql mariadb"`
Database string `json:"database" validate:"required"`
File string `json:"file"`
}
type ChangeDBInfo struct {
ID uint `json:"id"`
Value string `json:"value" validate:"required"`
ID uint `json:"id"`
From string `json:"from" validate:"required,oneof=local remote"`
Type string `json:"type" validate:"required,oneof=mysql mariadb"`
Database string `json:"database" validate:"required"`
Value string `json:"value" validate:"required"`
}
type DBBaseInfo struct {
@@ -109,30 +153,11 @@ type DBBaseInfo struct {
Port int64 `json:"port"`
}
type SearchDBWithPage struct {
PageInfo
MysqlName string `json:"mysqlName" validate:"required"`
}
type BackupDB struct {
MysqlName string `json:"mysqlName" validate:"required"`
DBName string `json:"dbName" validate:"required"`
}
type RecoverDB struct {
MysqlName string `json:"mysqlName" validate:"required"`
DBName string `json:"dbName" validate:"required"`
BackupName string `json:"backupName" validate:"required"`
}
type UploadRecover struct {
MysqlName string `json:"mysqlName" validate:"required"`
DBName string `json:"dbName" validate:"required"`
FileName string `json:"fileName"`
FileDir string `json:"fileDir"`
}
// redis
type ChangeRedisPass struct {
Value string `json:"value" validate:"required"`
}
type RedisConfUpdate struct {
Timeout string `json:"timeout"`
Maxclients string `json:"maxclients"`
@@ -191,3 +216,58 @@ type RedisBackupRecover struct {
FileName string `json:"fileName"`
FileDir string `json:"fileDir"`
}
// database
type DatabaseSearch struct {
PageInfo
Info string `json:"info"`
Type string `json:"type"`
OrderBy string `json:"orderBy"`
Order string `json:"order"`
}
type DatabaseInfo struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"`
Name string `json:"name" validate:"max=256"`
From string `json:"from"`
Type string `json:"type"`
Version string `json:"version"`
Address string `json:"address"`
Port uint `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
Description string `json:"description"`
}
type DatabaseOption struct {
ID uint `json:"id"`
Type string `json:"type"`
From string `json:"from"`
Database string `json:"database"`
Version string `json:"version"`
Address string `json:"address"`
}
type DatabaseCreate struct {
Name string `json:"name" validate:"required,max=256"`
Type string `json:"type" validate:"required"`
From string `json:"from" validate:"required,oneof=local remote"`
Version string `json:"version" validate:"required"`
Address string `json:"address"`
Port uint `json:"port"`
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
Description string `json:"description"`
}
type DatabaseUpdate struct {
ID uint `json:"id"`
Type string `json:"type" validate:"required"`
Version string `json:"version" validate:"required"`
Address string `json:"address"`
Port uint `json:"port"`
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
Description string `json:"description"`
}
+18 -2
View File
@@ -9,8 +9,10 @@ type FirewallBaseInfo struct {
type RuleSearch struct {
PageInfo
Info string `json:"info"`
Type string `json:"type" validate:"required"`
Info string `json:"info"`
Status string `json:"status"`
Strategy string `json:"strategy"`
Type string `json:"type" validate:"required"`
}
type FirewallOperation struct {
@@ -23,12 +25,26 @@ type PortRuleOperate struct {
Port string `json:"port" validate:"required"`
Protocol string `json:"protocol" validate:"required,oneof=tcp udp tcp/udp"`
Strategy string `json:"strategy" validate:"required,oneof=accept drop"`
Description string `json:"description"`
}
type UpdateFirewallDescription struct {
Type string `json:"type"`
Address string `json:"address"`
Port string `json:"port"`
Protocol string `json:"protocol"`
Strategy string `json:"strategy" validate:"required,oneof=accept drop"`
Description string `json:"description"`
}
type AddrRuleOperate struct {
Operation string `json:"operation" validate:"required,oneof=add remove"`
Address string `json:"address" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=accept drop"`
Description string `json:"description"`
}
type PortRuleUpdate struct {
+4 -4
View File
@@ -6,8 +6,8 @@ type ImageRepoCreate struct {
Name string `json:"name" validate:"required"`
DownloadUrl string `json:"downloadUrl"`
Protocol string `json:"protocol"`
Username string `json:"username"`
Password string `json:"password"`
Username string `json:"username" validate:"max=256"`
Password string `json:"password" validate:"max=256"`
Auth bool `json:"auth"`
}
@@ -15,8 +15,8 @@ type ImageRepoUpdate struct {
ID uint `json:"id"`
DownloadUrl string `json:"downloadUrl"`
Protocol string `json:"protocol"`
Username string `json:"username"`
Password string `json:"password"`
Username string `json:"username" validate:"max=256"`
Password string `json:"password" validate:"max=256"`
Auth bool `json:"auth"`
}
+6
View File
@@ -41,6 +41,11 @@ type AppInstalledSearch struct {
Unused bool `json:"unused"`
}
type AppInstalledInfo struct {
Key string `json:"key" validate:"required"`
Name string `json:"name"`
}
type AppBackupSearch struct {
dto.PageInfo
AppInstallID uint `json:"appInstallID"`
@@ -58,6 +63,7 @@ type AppInstalledOperate struct {
ForceDelete bool `json:"forceDelete"`
DeleteBackup bool `json:"deleteBackup"`
DeleteDB bool `json:"deleteDB"`
Backup bool `json:"backup"`
}
type AppInstalledUpdate struct {
+41
View File
@@ -0,0 +1,41 @@
package request
type HostToolReq struct {
Type string `json:"type" validate:"required,oneof=supervisord"`
Operate string `json:"operate" validate:"oneof=status restart start stop"`
}
type HostToolCreate struct {
Type string `json:"type" validate:"required"`
SupervisorConfig
}
type SupervisorConfig struct {
ConfigPath string `json:"configPath"`
ServiceName string `json:"serviceName"`
}
type HostToolLogReq struct {
Type string `json:"type" validate:"required,oneof=supervisord"`
}
type HostToolConfig struct {
Type string `json:"type" validate:"required,oneof=supervisord"`
Operate string `json:"operate" validate:"oneof=get set"`
Content string `json:"content"`
}
type SupervisorProcessConfig struct {
Name string `json:"name"`
Operate string `json:"operate"`
Command string `json:"command"`
User string `json:"user"`
Dir string `json:"dir"`
Numprocs string `json:"numprocs"`
}
type SupervisorProcessFileReq struct {
Name string `json:"name" validate:"required"`
Operate string `json:"operate" validate:"required,oneof=get clear update" `
Content string `json:"content"`
File string `json:"file" validate:"required,oneof=out.log err.log config"`
}
+22 -3
View File
@@ -3,9 +3,8 @@ package request
import "github.com/1Panel-dev/1Panel/backend/app/dto"
type NginxConfigFileUpdate struct {
Content string `json:"content" validate:"required"`
FilePath string `json:"filePath" validate:"required"`
Backup bool `json:"backup" validate:"required"`
Content string `json:"content" validate:"required"`
Backup bool `json:"backup" validate:"required"`
}
type NginxScopeReq struct {
@@ -66,3 +65,23 @@ type NginxAntiLeechUpdate struct {
LogEnable bool `json:"logEnable"`
Blocked bool `json:"blocked"`
}
type NginxRedirectReq struct {
Name string `json:"name" validate:"required"`
WebsiteID uint `json:"websiteID" validate:"required"`
Domains []string `json:"domains"`
KeepPath bool `json:"keepPath" validate:"required"`
Enable bool `json:"enable" validate:"required"`
Type string `json:"type" validate:"required"`
Redirect string `json:"redirect" validate:"required"`
Path string `json:"path"`
Target string `json:"target" validate:"required"`
Operate string `json:"operate" validate:"required"`
RedirectRoot bool `json:"redirectRoot"`
}
type NginxRedirectUpdate struct {
WebsiteID uint `json:"websiteID" validate:"required"`
Content string `json:"content" validate:"required"`
Name string `json:"name" validate:"required"`
}
+3
View File
@@ -17,6 +17,7 @@ type RuntimeCreate struct {
Image string `json:"image"`
Type string `json:"type"`
Version string `json:"version"`
Source string `json:"source"`
}
type RuntimeDelete struct {
@@ -29,4 +30,6 @@ type RuntimeUpdate struct {
Params map[string]interface{} `json:"params"`
Image string `json:"image"`
Version string `json:"version"`
Rebuild bool `json:"rebuild"`
Source string `json:"source"`
}
+31 -3
View File
@@ -75,6 +75,12 @@ type WebsiteWafReq struct {
Rule string `json:"rule" validate:"required"`
}
type WebsiteRedirectUpdate struct {
WebsiteID uint `json:"websiteId" validate:"required"`
Key string `json:"key" validate:"required"`
Enable bool `json:"enable" validate:"required"`
}
type WebsiteWafUpdate struct {
WebsiteID uint `json:"websiteId" validate:"required"`
Key string `json:"key" validate:"required"`
@@ -135,9 +141,11 @@ type WebsiteNginxUpdate struct {
}
type WebsiteLogReq struct {
ID uint `json:"id" validate:"required"`
Operate string `json:"operate" validate:"required"`
LogType string `json:"logType" validate:"required"`
ID uint `json:"id" validate:"required"`
Operate string `json:"operate" validate:"required"`
LogType string `json:"logType" validate:"required"`
Page int `json:"page"`
PageSize int `json:"pageSize"`
}
type WebsiteDefaultUpdate struct {
@@ -158,6 +166,12 @@ type WebsitePHPFileUpdate struct {
Content string `json:"content" validate:"required"`
}
type WebsitePHPVersionReq struct {
WebsiteID uint `json:"websiteID" validate:"required"`
RuntimeID uint `json:"runtimeID" validate:"required"`
RetainConfig bool `json:"retainConfig" validate:"required"`
}
type WebsiteUpdateDir struct {
ID uint `json:"id" validate:"required"`
SiteDir string `json:"siteDir" validate:"required"`
@@ -189,3 +203,17 @@ type WebsiteProxyConfig struct {
type WebsiteProxyReq struct {
ID uint `json:"id" validate:"required"`
}
type WebsiteRedirectReq struct {
WebsiteID uint `json:"websiteId" validate:"required"`
}
type WebsiteWafFileUpdate struct {
WebsiteID uint `json:"websiteID" validate:"required"`
Content string `json:"content" validate:"required"`
Type string `json:"type" validate:"required,oneof=cc ip_white ip_block url_white url_block cookie_block args_check post_check ua_check file_ext_block"`
}
type WebsiteCommonReq struct {
ID uint `json:"id" validate:"required"`
}
+1 -1
View File
@@ -4,7 +4,7 @@ import "github.com/1Panel-dev/1Panel/backend/app/dto"
type WebsiteSSLSearch struct {
dto.PageInfo
AcmeAccountID uint `json:"acmeAccountId"`
AcmeAccountID string `json:"acmeAccountID"`
}
type WebsiteSSLCreate struct {
+2
View File
@@ -39,6 +39,8 @@ type AppInstalledCheck struct {
AppInstallID uint `json:"appInstallId"`
ContainerName string `json:"containerName"`
InstallPath string `json:"installPath"`
HttpPort int `json:"httpPort"`
HttpsPort int `json:"httpsPort"`
}
type AppDetailDTO struct {
+41
View File
@@ -0,0 +1,41 @@
package response
type HostToolRes struct {
Type string `json:"type"`
Config interface{} `json:"config"`
}
type Supervisor struct {
ConfigPath string `json:"configPath"`
IncludeDir string `json:"includeDir"`
LogPath string `json:"logPath"`
IsExist bool `json:"isExist"`
Init bool `json:"init"`
Msg string `json:"msg"`
Version string `json:"version"`
Status string `json:"status"`
CtlExist bool `json:"ctlExist"`
ServiceName string `json:"serviceName"`
}
type HostToolConfig struct {
Content string `json:"content"`
}
type SupervisorProcessConfig struct {
Name string `json:"name"`
Command string `json:"command"`
User string `json:"user"`
Dir string `json:"dir"`
Numprocs string `json:"numprocs"`
Msg string `json:"msg"`
Status []ProcessStatus `json:"status"`
}
type ProcessStatus struct {
Name string `json:"name"`
Status string `json:"status"`
PID string `json:"PID"`
Uptime string `json:"uptime"`
Msg string `json:"msg"`
}
+19
View File
@@ -34,3 +34,22 @@ type NginxAntiLeechRes struct {
LogEnable bool `json:"logEnable"`
Blocked bool `json:"blocked"`
}
type NginxRedirectConfig struct {
WebsiteID uint `json:"websiteID"`
Name string `json:"name"`
Domains []string `json:"domains"`
KeepPath bool `json:"keepPath"`
Enable bool `json:"enable"`
Type string `json:"type"`
Redirect string `json:"redirect"`
Path string `json:"path"`
Target string `json:"target"`
FilePath string `json:"filePath"`
Content string `json:"content"`
RedirectRoot bool `json:"redirectRoot"`
}
type NginxFile struct {
Content string `json:"content"`
}
+1
View File
@@ -6,4 +6,5 @@ type RuntimeRes struct {
model.Runtime
AppParams []AppParam `json:"appParams"`
AppID uint `json:"appId"`
Source string `json:"source"`
}
+11 -3
View File
@@ -26,9 +26,8 @@ type WebsiteNginxConfig struct {
}
type WebsiteWafConfig struct {
Enable bool `json:"enable"`
FilePath string `json:"filePath"`
Content string `json:"content"`
Enable bool `json:"enable"`
Content string `json:"content"`
}
type WebsiteHTTPS struct {
@@ -42,6 +41,8 @@ type WebsiteHTTPS struct {
type WebsiteLog struct {
Enable bool `json:"enable"`
Content string `json:"content"`
End bool `json:"end"`
Path string `json:"path"`
}
type PHPConfig struct {
@@ -53,3 +54,10 @@ type PHPConfig struct {
type NginxRewriteRes struct {
Content string `json:"content"`
}
type WebsiteDirConfig struct {
Dirs []string `json:"dirs"`
User string `json:"user"`
UserGroup string `json:"userGroup"`
Msg string `json:"msg"`
}
+14
View File
@@ -75,7 +75,21 @@ type PortUpdate struct {
ServerPort uint `json:"serverPort" validate:"required,number,max=65535,min=1"`
}
type SnapshotStatus struct {
Panel string `json:"panel"`
PanelInfo string `json:"panelInfo"`
DaemonJson string `json:"daemonJson"`
AppData string `json:"appData"`
PanelData string `json:"panelData"`
BackupData string `json:"backupData"`
Compress string `json:"compress"`
Size string `json:"size"`
Upload string `json:"upload"`
}
type SnapshotCreate struct {
ID uint `json:"id"`
From string `json:"from" validate:"required,oneof=OSS S3 SFTP MINIO COS KODO OneDrive"`
Description string `json:"description" validate:"max=256"`
}
@@ -6,4 +6,5 @@ type AppInstallResource struct {
LinkId uint `json:"linkId" gorm:"type:integer;not null;"`
ResourceId uint `json:"resourceId" gorm:"type:integer;"`
Key string `json:"key" gorm:"type:varchar(64);not null"`
From string `json:"from" gorm:"type:varchar(64);not null;default:local"`
}
+1
View File
@@ -18,6 +18,7 @@ type Cronjob struct {
ContainerName string `gorm:"type:varchar(64)" json:"containerName"`
Script string `gorm:"longtext" json:"script"`
Website string `gorm:"type:varchar(64)" json:"website"`
AppID string `gorm:"type:varchar(64)" json:"appID"`
DBName string `gorm:"type:varchar(64)" json:"dbName"`
URL string `gorm:"type:varchar(256)" json:"url"`
SourceDir string `gorm:"type:varchar(256)" json:"sourceDir"`
+15
View File
@@ -0,0 +1,15 @@
package model
type Database struct {
BaseModel
AppInstallID uint `json:"appInstallID" gorm:"type:decimal"`
Name string `json:"name" gorm:"type:varchar(64);not null;unique"`
Type string `json:"type" gorm:"type:varchar(64);not null"`
Version string `json:"version" gorm:"type:varchar(64);not null"`
From string `json:"from" gorm:"type:varchar(64);not null"`
Address string `json:"address" gorm:"type:varchar(64);not null"`
Port uint `json:"port" gorm:"type:decimal;not null"`
Username string `json:"username" gorm:"type:varchar(64)"`
Password string `json:"password" gorm:"type:varchar(64)"`
Description string `json:"description" gorm:"type:varchar(256);"`
}
+1
View File
@@ -3,6 +3,7 @@ package model
type DatabaseMysql struct {
BaseModel
Name string `json:"name" gorm:"type:varchar(256);not null"`
From string `json:"from" gorm:"type:varchar(256);not null;default:local"`
MysqlName string `json:"mysqlName" gorm:"type:varchar(64);not null"`
Format string `json:"format" gorm:"type:varchar(64);not null"`
Username string `json:"username" gorm:"type:varchar(256);not null"`
+12
View File
@@ -0,0 +1,12 @@
package model
type Firewall struct {
BaseModel
Type string `gorm:"type:varchar(64);not null" json:"type"`
Port string `gorm:"type:varchar(64);not null" json:"port"`
Protocol string `gorm:"type:varchar(64);not null" json:"protocol"`
Address string `gorm:"type:varchar(64);not null" json:"address"`
Strategy string `gorm:"type:varchar(64);not null" json:"strategy"`
Description string `gorm:"type:varchar(64);not null" json:"description"`
}
+15
View File
@@ -17,3 +17,18 @@ type Snapshot struct {
RollbackMessage string `json:"rollbackMessage" gorm:"type:varchar(256)"`
LastRollbackedAt string `json:"lastRollbackedAt" gorm:"type:varchar(64)"`
}
type SnapshotStatus struct {
BaseModel
SnapID uint `gorm:"type:decimal" json:"snapID"`
Panel string `json:"panel" gorm:"type:varchar(64);default:Running"`
PanelInfo string `json:"panelInfo" gorm:"type:varchar(64);default:Running"`
DaemonJson string `json:"daemonJson" gorm:"type:varchar(64);default:Running"`
AppData string `json:"appData" gorm:"type:varchar(64);default:Running"`
PanelData string `json:"panelData" gorm:"type:varchar(64);default:Running"`
BackupData string `json:"backupData" gorm:"type:varchar(64);default:Running"`
Compress string `json:"compress" gorm:"type:varchar(64);default:Waiting"`
Size string `json:"size" gorm:"type:varchar(64)"`
Upload string `json:"upload" gorm:"type:varchar(64);default:Waiting"`
}
+1 -1
View File
@@ -66,7 +66,7 @@ func (a AppRepo) Page(page, size int, opts ...DBOption) (int64, []model.App, err
db := getDb(opts...).Model(&model.App{})
count := int64(0)
db = db.Count(&count)
err := db.Debug().Limit(size).Offset(size * (page - 1)).Preload("AppTags").Find(&apps).Error
err := db.Limit(size).Offset(size * (page - 1)).Preload("AppTags").Find(&apps).Error
return count, apps, err
}
+25 -4
View File
@@ -3,6 +3,7 @@ package repo
import (
"context"
"encoding/json"
"gorm.io/gorm/clause"
"github.com/1Panel-dev/1Panel/backend/app/model"
@@ -126,7 +127,7 @@ func (a *AppInstallRepo) Create(ctx context.Context, install *model.AppInstall)
}
func (a *AppInstallRepo) Save(ctx context.Context, install *model.AppInstall) error {
return getTx(ctx).Omit(clause.Associations).Save(&install).Error
return getTx(ctx).Save(&install).Error
}
func (a *AppInstallRepo) DeleteBy(opts ...DBOption) error {
@@ -160,6 +161,7 @@ type RootInfo struct {
Name string `json:"name"`
Port int64 `json:"port"`
HttpsPort int64 `json:"httpsPort"`
UserName string `json:"userName"`
Password string `json:"password"`
UserPassword string `json:"userPassword"`
ContainerName string `json:"containerName"`
@@ -192,10 +194,28 @@ func (a *AppInstallRepo) LoadBaseInfo(key string, name string) (*RootInfo, error
if err := json.Unmarshal([]byte(appInstall.Env), &envMap); err != nil {
return nil, err
}
password, ok := envMap["PANEL_DB_ROOT_PASSWORD"].(string)
if ok {
info.Password = password
switch app.Key {
case "mysql", "mariadb":
password, ok := envMap["PANEL_DB_ROOT_PASSWORD"].(string)
if ok {
info.Password = password
}
case "redis":
password, ok := envMap["PANEL_REDIS_ROOT_PASSWORD"].(string)
if ok {
info.Password = password
}
case "mongodb", "postgresql":
user, ok := envMap["PANEL_DB_ROOT_USER"].(string)
if ok {
info.UserName = user
}
password, ok := envMap["PANEL_DB_ROOT_PASSWORD"].(string)
if ok {
info.Password = password
}
}
userPassword, ok := envMap["PANEL_DB_USER_PASSWORD"].(string)
if ok {
info.UserPassword = userPassword
@@ -209,5 +229,6 @@ func (a *AppInstallRepo) LoadBaseInfo(key string, name string) (*RootInfo, error
info.Env = appInstall.Env
info.Param = appInstall.Param
info.Version = appInstall.Version
info.Key = app.Key
return &info, nil
}
+143
View File
@@ -0,0 +1,143 @@
package repo
import (
"context"
"fmt"
"strings"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
"gorm.io/gorm"
)
type DatabaseRepo struct{}
type IDatabaseRepo interface {
GetList(opts ...DBOption) ([]model.Database, error)
Page(limit, offset int, opts ...DBOption) (int64, []model.Database, error)
Create(ctx context.Context, database *model.Database) error
Update(id uint, vars map[string]interface{}) error
Delete(ctx context.Context, opts ...DBOption) error
Get(opts ...DBOption) (model.Database, error)
WithByFrom(from string) DBOption
WithoutByFrom(from string) DBOption
WithAppInstallID(appInstallID uint) DBOption
WithTypeList(dbType string) DBOption
}
func NewIDatabaseRepo() IDatabaseRepo {
return &DatabaseRepo{}
}
func (d *DatabaseRepo) Get(opts ...DBOption) (model.Database, error) {
var database model.Database
db := global.DB
for _, opt := range opts {
db = opt(db)
}
if err := db.First(&database).Error; err != nil {
return database, err
}
pass, err := encrypt.StringDecrypt(database.Password)
if err != nil {
global.LOG.Errorf("decrypt database %s password failed, err: %v", database.Name, err)
}
database.Password = pass
return database, nil
}
func (d *DatabaseRepo) Page(page, size int, opts ...DBOption) (int64, []model.Database, error) {
var databases []model.Database
db := global.DB.Model(&model.Database{})
for _, opt := range opts {
db = opt(db)
}
count := int64(0)
db = db.Count(&count)
if err := db.Limit(size).Offset(size * (page - 1)).Find(&databases).Error; err != nil {
return count, databases, err
}
for i := 0; i < len(databases); i++ {
pass, err := encrypt.StringDecrypt(databases[i].Password)
if err != nil {
global.LOG.Errorf("decrypt database db %s password failed, err: %v", databases[i].Name, err)
}
databases[i].Password = pass
}
return count, databases, nil
}
func (d *DatabaseRepo) GetList(opts ...DBOption) ([]model.Database, error) {
var databases []model.Database
db := global.DB.Model(&model.Database{})
for _, opt := range opts {
db = opt(db)
}
if err := db.Find(&databases).Error; err != nil {
return databases, err
}
for i := 0; i < len(databases); i++ {
pass, err := encrypt.StringDecrypt(databases[i].Password)
if err != nil {
global.LOG.Errorf("decrypt database db %s password failed, err: %v", databases[i].Name, err)
}
databases[i].Password = pass
}
return databases, nil
}
func (d *DatabaseRepo) WithByFrom(from string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("`from` = ?", from)
}
}
func (d *DatabaseRepo) WithoutByFrom(from string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("`from` != ?", from)
}
}
func (d *DatabaseRepo) WithTypeList(dbType string) DBOption {
return func(g *gorm.DB) *gorm.DB {
if !strings.Contains(dbType, ",") {
return g.Where("`type` = ?", dbType)
}
types := strings.Split(dbType, ",")
var (
rules []string
values []interface{}
)
for _, ty := range types {
if len(ty) != 0 {
rules = append(rules, "`type` = ?")
values = append(values, ty)
}
}
return g.Where(strings.Join(rules, " OR "), values...)
}
}
func (d *DatabaseRepo) WithAppInstallID(appInstallID uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("app_install_id = ?", appInstallID)
}
}
func (d *DatabaseRepo) Create(ctx context.Context, database *model.Database) error {
pass, err := encrypt.StringEncrypt(database.Password)
if err != nil {
return fmt.Errorf("decrypt database db %s password failed, err: %v", database.Name, err)
}
database.Password = pass
return getTx(ctx).Create(database).Error
}
func (d *DatabaseRepo) Update(id uint, vars map[string]interface{}) error {
return global.DB.Model(&model.Database{}).Where("id = ?", id).Updates(vars).Error
}
func (d *DatabaseRepo) Delete(ctx context.Context, opts ...DBOption) error {
return getTx(ctx, opts...).Delete(&model.Database{}).Error
}
+53 -18
View File
@@ -2,9 +2,11 @@ package repo
import (
"context"
"fmt"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
"gorm.io/gorm"
)
@@ -13,13 +15,13 @@ type MysqlRepo struct{}
type IMysqlRepo interface {
Get(opts ...DBOption) (model.DatabaseMysql, error)
WithByMysqlName(mysqlName string) DBOption
WithByFrom(from string) DBOption
List(opts ...DBOption) ([]model.DatabaseMysql, error)
Page(limit, offset int, opts ...DBOption) (int64, []model.DatabaseMysql, error)
Create(ctx context.Context, mysql *model.DatabaseMysql) error
Delete(ctx context.Context, opts ...DBOption) error
Update(id uint, vars map[string]interface{}) error
UpdateDatabaseInfo(id uint, vars map[string]interface{}) error
DeleteAll(ctx context.Context) error
DeleteLocal(ctx context.Context) error
}
func NewIMysqlRepo() IMysqlRepo {
@@ -32,33 +34,64 @@ func (u *MysqlRepo) Get(opts ...DBOption) (model.DatabaseMysql, error) {
for _, opt := range opts {
db = opt(db)
}
err := db.First(&mysql).Error
if err := db.First(&mysql).Error; err != nil {
return mysql, err
}
pass, err := encrypt.StringDecrypt(mysql.Password)
if err != nil {
global.LOG.Errorf("decrypt database db %s password failed, err: %v", mysql.Name, err)
}
mysql.Password = pass
return mysql, err
}
func (u *MysqlRepo) List(opts ...DBOption) ([]model.DatabaseMysql, error) {
var users []model.DatabaseMysql
var mysqls []model.DatabaseMysql
db := global.DB.Model(&model.DatabaseMysql{})
for _, opt := range opts {
db = opt(db)
}
err := db.Find(&users).Error
return users, err
if err := db.Find(&mysqls).Error; err != nil {
return mysqls, err
}
for i := 0; i < len(mysqls); i++ {
pass, err := encrypt.StringDecrypt(mysqls[i].Password)
if err != nil {
global.LOG.Errorf("decrypt database db %s password failed, err: %v", mysqls[i].Name, err)
}
mysqls[i].Password = pass
}
return mysqls, nil
}
func (u *MysqlRepo) Page(page, size int, opts ...DBOption) (int64, []model.DatabaseMysql, error) {
var users []model.DatabaseMysql
var mysqls []model.DatabaseMysql
db := global.DB.Model(&model.DatabaseMysql{})
for _, opt := range opts {
db = opt(db)
}
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error
return count, users, err
if err := db.Limit(size).Offset(size * (page - 1)).Find(&mysqls).Error; err != nil {
return count, mysqls, err
}
for i := 0; i < len(mysqls); i++ {
pass, err := encrypt.StringDecrypt(mysqls[i].Password)
if err != nil {
global.LOG.Errorf("decrypt database db %s password failed, err: %v", mysqls[i].Name, err)
}
mysqls[i].Password = pass
}
return count, mysqls, nil
}
func (u *MysqlRepo) Create(ctx context.Context, mysql *model.DatabaseMysql) error {
pass, err := encrypt.StringEncrypt(mysql.Password)
if err != nil {
return fmt.Errorf("decrypt database db %s password failed, err: %v", mysql.Name, err)
}
mysql.Password = pass
return getTx(ctx).Create(mysql).Error
}
@@ -66,23 +99,25 @@ func (u *MysqlRepo) Delete(ctx context.Context, opts ...DBOption) error {
return getTx(ctx, opts...).Delete(&model.DatabaseMysql{}).Error
}
func (u *MysqlRepo) DeleteAll(ctx context.Context) error {
return getTx(ctx).Where("1 = 1").Delete(&model.DatabaseMysql{}).Error
func (u *MysqlRepo) DeleteLocal(ctx context.Context) error {
return getTx(ctx).Where("`from` = ?", "local").Delete(&model.DatabaseMysql{}).Error
}
func (u *MysqlRepo) Update(id uint, vars map[string]interface{}) error {
return global.DB.Model(&model.DatabaseMysql{}).Where("id = ?", id).Updates(vars).Error
}
func (u *MysqlRepo) UpdateDatabaseInfo(id uint, vars map[string]interface{}) error {
if err := global.DB.Model(&model.AppInstall{}).Where("id = ?", id).Updates(vars).Error; err != nil {
return err
}
return nil
}
func (u *MysqlRepo) WithByMysqlName(mysqlName string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("mysql_name = ?", mysqlName)
}
}
func (u *MysqlRepo) WithByFrom(from string) DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(from) != 0 {
return g.Where("`from` = ?", from)
}
return g
}
}
+51
View File
@@ -19,6 +19,12 @@ type IHostRepo interface {
Create(host *model.Host) error
Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) error
GetFirewallRecord(opts ...DBOption) (model.Firewall, error)
ListFirewallRecord() ([]model.Firewall, error)
SaveFirewallRecord(firewall *model.Firewall) error
DeleteFirewallRecordByID(id uint) error
DeleteFirewallRecord(fType, port, protocol, address, strategy string) error
}
func NewIHostRepo() IHostRepo {
@@ -106,3 +112,48 @@ func (h *HostRepo) Delete(opts ...DBOption) error {
}
return db.Delete(&model.Host{}).Error
}
func (h *HostRepo) GetFirewallRecord(opts ...DBOption) (model.Firewall, error) {
var firewall model.Firewall
db := global.DB
for _, opt := range opts {
db = opt(db)
}
err := db.First(&firewall).Error
return firewall, err
}
func (h *HostRepo) ListFirewallRecord() ([]model.Firewall, error) {
var datas []model.Firewall
if err := global.DB.Find(&datas).Error; err != nil {
return datas, nil
}
return datas, nil
}
func (h *HostRepo) SaveFirewallRecord(firewall *model.Firewall) error {
if firewall.ID != 0 {
return global.DB.Save(firewall).Error
}
var data model.Firewall
if firewall.Type == "port" {
_ = global.DB.Where("type = ? AND port = ? AND protocol = ? AND address = ? AND strategy = ?", "port", firewall.Port, firewall.Protocol, firewall.Address, firewall.Strategy).First(&data)
if data.ID != 0 {
firewall.ID = data.ID
}
} else {
_ = global.DB.Where("type = ? AND address = ? AND strategy = ?", "address", firewall.Address, firewall.Strategy).First(&data)
if data.ID != 0 {
firewall.ID = data.ID
}
}
return global.DB.Save(firewall).Error
}
func (h *HostRepo) DeleteFirewallRecordByID(id uint) error {
return global.DB.Where("id = ?", id).Delete(&model.Firewall{}).Error
}
func (h *HostRepo) DeleteFirewallRecord(fType, port, protocol, address, strategy string) error {
return global.DB.Where("type = ? AND port = ? AND protocol = ? AND address = ? AND strategy = ?", fType, port, protocol, address, strategy).Delete(&model.Firewall{}).Error
}
+13
View File
@@ -2,7 +2,9 @@ package repo
import (
"context"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/global"
"gorm.io/gorm"
)
@@ -20,6 +22,7 @@ type IRuntimeRepo interface {
Save(runtime *model.Runtime) error
DeleteBy(opts ...DBOption) error
GetFirst(opts ...DBOption) (*model.Runtime, error)
List(opts ...DBOption) ([]model.Runtime, error)
}
func NewIRunTimeRepo() IRuntimeRepo {
@@ -65,6 +68,16 @@ func (r *RuntimeRepo) Page(page, size int, opts ...DBOption) (int64, []model.Run
return count, runtimes, err
}
func (r *RuntimeRepo) List(opts ...DBOption) ([]model.Runtime, error) {
var runtimes []model.Runtime
db := global.DB.Model(&model.Runtime{})
for _, opt := range opts {
db = opt(db)
}
err := db.Find(&runtimes).Error
return runtimes, err
}
func (r *RuntimeRepo) Create(ctx context.Context, runtime *model.Runtime) error {
db := getTx(ctx).Model(&model.Runtime{})
return db.Create(&runtime).Error
+36
View File
@@ -12,6 +12,12 @@ type ISnapshotRepo interface {
Update(id uint, vars map[string]interface{}) error
Page(limit, offset int, opts ...DBOption) (int64, []model.Snapshot, error)
Delete(opts ...DBOption) error
GetStatus(snapID uint) (model.SnapshotStatus, error)
GetStatusList(opts ...DBOption) ([]model.SnapshotStatus, error)
CreateStatus(snap *model.SnapshotStatus) error
DeleteStatus(snapID uint) error
UpdateStatus(id uint, vars map[string]interface{}) error
}
func NewISnapshotRepo() ISnapshotRepo {
@@ -67,3 +73,33 @@ func (u *SnapshotRepo) Delete(opts ...DBOption) error {
}
return db.Delete(&model.Snapshot{}).Error
}
func (u *SnapshotRepo) GetStatus(snapID uint) (model.SnapshotStatus, error) {
var data model.SnapshotStatus
if err := global.DB.Where("snap_id = ?", snapID).First(&data).Error; err != nil {
return data, err
}
return data, nil
}
func (u *SnapshotRepo) GetStatusList(opts ...DBOption) ([]model.SnapshotStatus, error) {
var status []model.SnapshotStatus
db := global.DB.Model(&model.SnapshotStatus{})
for _, opt := range opts {
db = opt(db)
}
err := db.Find(&status).Error
return status, err
}
func (u *SnapshotRepo) CreateStatus(snap *model.SnapshotStatus) error {
return global.DB.Create(snap).Error
}
func (u *SnapshotRepo) DeleteStatus(snapID uint) error {
return global.DB.Where("snap_id = ?", snapID).Delete(&model.SnapshotStatus{}).Error
}
func (u *SnapshotRepo) UpdateStatus(id uint, vars map[string]interface{}) error {
return global.DB.Model(&model.SnapshotStatus{}).Where("id = ?", id).Updates(vars).Error
}
+5
View File
@@ -26,6 +26,7 @@ type IWebsiteRepo interface {
SaveWithoutCtx(app *model.Website) error
DeleteBy(ctx context.Context, opts ...DBOption) error
Create(ctx context.Context, app *model.Website) error
DeleteAll(ctx context.Context) error
}
func NewIWebsiteRepo() IWebsiteRepo {
@@ -131,3 +132,7 @@ func (w *WebsiteRepo) SaveWithoutCtx(website *model.Website) error {
func (w *WebsiteRepo) DeleteBy(ctx context.Context, opts ...DBOption) error {
return getTx(ctx, opts...).Delete(&model.Website{}).Error
}
func (w *WebsiteRepo) DeleteAll(ctx context.Context) error {
return getTx(ctx).Where("1 = 1 ").Delete(&model.Website{}).Error
}
+5
View File
@@ -21,6 +21,7 @@ type IWebsiteDomainRepo interface {
Create(ctx context.Context, app *model.WebsiteDomain) error
Save(ctx context.Context, app *model.WebsiteDomain) error
DeleteBy(ctx context.Context, opts ...DBOption) error
DeleteAll(ctx context.Context) error
}
func NewIWebsiteDomainRepo() IWebsiteDomainRepo {
@@ -85,3 +86,7 @@ func (w WebsiteDomainRepo) Save(ctx context.Context, app *model.WebsiteDomain) e
func (w WebsiteDomainRepo) DeleteBy(ctx context.Context, opts ...DBOption) error {
return getTx(ctx, opts...).Delete(&model.WebsiteDomain{}).Error
}
func (w WebsiteDomainRepo) DeleteAll(ctx context.Context) error {
return getTx(ctx).Where("1 = 1 ").Delete(&model.WebsiteDomain{}).Error
}
+100 -52
View File
@@ -5,6 +5,14 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path"
"reflect"
"strconv"
"strings"
"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"
@@ -19,12 +27,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/utils/files"
http2 "github.com/1Panel-dev/1Panel/backend/utils/http"
"gopkg.in/yaml.v3"
"io"
"net/http"
"os"
"path"
"strconv"
"strings"
)
type AppService struct {
@@ -274,6 +276,20 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
appDetail model.AppDetail
app model.App
)
appDetail, err = appDetailRepo.GetFirst(commonRepo.WithByID(req.AppDetailId))
if err != nil {
return
}
app, err = appRepo.GetFirst(commonRepo.WithByID(appDetail.AppId))
if err != nil {
return
}
if DatabaseKeys[app.Key] > 0 {
if existDatabases, _ := databaseRepo.GetList(commonRepo.WithByName(req.Name)); len(existDatabases) > 0 {
err = buserr.New(constant.ErrRemoteExist)
return
}
}
for key := range req.Params {
if !strings.Contains(key, "PANEL_APP_PORT") {
continue
@@ -291,14 +307,6 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
}
}
appDetail, err = appDetailRepo.GetFirst(commonRepo.WithByID(req.AppDetailId))
if err != nil {
return
}
app, err = appRepo.GetFirst(commonRepo.WithByID(appDetail.AppId))
if err != nil {
return
}
if err = checkRequiredAndLimit(app); err != nil {
return
}
@@ -352,13 +360,20 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
appInstall.ContainerName = containerName
index := 0
serviceName := ""
for k := range servicesMap {
appInstall.ServiceName = k
serviceName = k
if index > 0 {
continue
}
index++
}
if app.Limit == 0 && appInstall.Name != serviceName {
servicesMap[appInstall.Name] = servicesMap[serviceName]
delete(servicesMap, serviceName)
serviceName = appInstall.Name
}
appInstall.ServiceName = serviceName
if err = addDockerComposeCommonParam(composeMap, appInstall.ServiceName, req.AppContainerConfig, req.Params); err != nil {
return
@@ -382,6 +397,14 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
}
}
}()
if hostName, ok := req.Params["PANEL_DB_HOST"]; ok {
database, _ := databaseRepo.Get(commonRepo.WithByName(hostName.(string)))
if !reflect.DeepEqual(database, model.Database{}) {
req.Params["PANEL_DB_HOST"] = database.Address
req.Params["PANEL_DB_PORT"] = database.Port
req.Params["PANEL_DB_HOST_NAME"] = hostName
}
}
paramByte, err = json.Marshal(req.Params)
if err != nil {
return
@@ -394,15 +417,17 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
return
}
go func() {
if err = copyData(app, appDetail, appInstall, req); err != nil {
if appInstall.Status == constant.Installing {
defer func() {
if err != nil {
appInstall.Status = constant.Error
appInstall.Message = err.Error()
_ = appInstallRepo.Save(context.Background(), appInstall)
}
_ = appInstallRepo.Save(context.Background(), appInstall)
}()
if err = copyData(app, appDetail, appInstall, req); err != nil {
return
}
if err = upAppPre(app, appInstall); err != nil {
if err = runScript(appInstall, "init"); err != nil {
return
}
upApp(appInstall)
@@ -663,10 +688,26 @@ func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) {
return nil, err
}
appStoreLastModified, _ := strconv.Atoi(setting.AppStoreLastModified)
res.AppStoreLastModified = appStoreLastModified
if setting.AppStoreLastModified == "" || lastModified != appStoreLastModified {
res.CanUpdate = true
return res, err
}
if err = getAppFromRepo(fmt.Sprintf("%s/%s/1panel.json.zip", global.CONF.System.AppRepo, global.CONF.System.Mode)); err != nil {
return nil, err
}
listFile := path.Join(constant.ResourceDir, "1panel.json")
content, err := os.ReadFile(listFile)
if err != nil {
return nil, err
}
list := &dto.AppList{}
if err = json.Unmarshal(content, list); err != nil {
return nil, err
}
if list.Extra.Version != "" && setting.SystemVersion != list.Extra.Version && !common.CompareVersion(setting.SystemVersion, list.Extra.Version) {
return nil, buserr.New("ErrVersionTooLow")
}
return res, nil
}
@@ -687,13 +728,13 @@ func getAppFromRepo(downloadPath string) error {
return nil
}
func (a AppService) SyncAppListFromRemote() error {
func (a AppService) SyncAppListFromRemote() (err error) {
updateRes, err := a.GetAppUpdate()
if err != nil {
return err
}
if !updateRes.CanUpdate {
return nil
return
}
if err = getAppFromRepo(fmt.Sprintf("%s/%s/1panel.json.zip", global.CONF.System.AppRepo, global.CONF.System.Mode)); err != nil {
return err
@@ -704,10 +745,20 @@ func (a AppService) SyncAppListFromRemote() error {
return err
}
list := &dto.AppList{}
if err := json.Unmarshal(content, list); err != nil {
if err = json.Unmarshal(content, list); err != nil {
return
}
if err = NewISettingService().Update("AppStoreLastModified", strconv.Itoa(list.LastModified)); err != nil {
return err
}
defer func() {
if err != nil {
_ = NewISettingService().Update("AppStoreLastModified", strconv.Itoa(updateRes.AppStoreLastModified))
}
}()
var (
tags []*model.Tag
appTags []*model.AppTag
@@ -721,7 +772,7 @@ func (a AppService) SyncAppListFromRemote() error {
}
oldApps, err := appRepo.GetBy(appRepo.WithResource(constant.AppResourceRemote))
if err != nil {
return err
return
}
for _, old := range oldApps {
oldAppIds = append(oldAppIds, old.ID)
@@ -754,8 +805,8 @@ func (a AppService) SyncAppListFromRemote() error {
for _, v := range versions {
version := v.Name
detail := detailsMap[version]
dockerComposeUrl := fmt.Sprintf("%s/%s/%s/%s", baseRemoteUrl, app.Key, version, "docker-compose.yml")
versionUrl := fmt.Sprintf("%s/%s/%s", baseRemoteUrl, app.Key, version)
dockerComposeUrl := fmt.Sprintf("%s/%s", versionUrl, "docker-compose.yml")
composeRes, err := http.Get(dockerComposeUrl)
if err != nil {
return err
@@ -768,7 +819,7 @@ func (a AppService) SyncAppListFromRemote() error {
paramByte, _ := json.Marshal(v.AppForm)
detail.Params = string(paramByte)
detail.DownloadUrl = v.DownloadUrl
detail.DownloadUrl = fmt.Sprintf("%s/%s", versionUrl, app.Key+"-"+version+".tar.gz")
detail.DownloadCallBackUrl = v.DownloadCallBackUrl
detail.Update = true
detail.LastModified = v.LastModified
@@ -810,32 +861,32 @@ func (a AppService) SyncAppListFromRemote() error {
tx, ctx := getTxAndContext()
defer tx.Rollback()
if len(addAppArray) > 0 {
if err := appRepo.BatchCreate(ctx, addAppArray); err != nil {
return err
if err = appRepo.BatchCreate(ctx, addAppArray); err != nil {
return
}
}
if len(deleteAppArray) > 0 {
if err := appRepo.BatchDelete(ctx, deleteAppArray); err != nil {
return err
if err = appRepo.BatchDelete(ctx, deleteAppArray); err != nil {
return
}
if err := appDetailRepo.DeleteByAppIds(ctx, deleteIds); err != nil {
return err
if err = appDetailRepo.DeleteByAppIds(ctx, deleteIds); err != nil {
return
}
}
if err := tagRepo.DeleteAll(ctx); err != nil {
return err
if err = tagRepo.DeleteAll(ctx); err != nil {
return
}
if len(tags) > 0 {
if err := tagRepo.BatchCreate(ctx, tags); err != nil {
return err
if err = tagRepo.BatchCreate(ctx, tags); err != nil {
return
}
for _, t := range tags {
tagMap[t.Key] = t.ID
}
}
for _, update := range updateAppArray {
if err := appRepo.Save(ctx, &update); err != nil {
return err
if err = appRepo.Save(ctx, &update); err != nil {
return
}
}
apps := append(addAppArray, updateAppArray...)
@@ -879,35 +930,32 @@ func (a AppService) SyncAppListFromRemote() error {
}
}
if len(addDetails) > 0 {
if err := appDetailRepo.BatchCreate(ctx, addDetails); err != nil {
return err
if err = appDetailRepo.BatchCreate(ctx, addDetails); err != nil {
return
}
}
if len(deleteDetails) > 0 {
if err := appDetailRepo.BatchDelete(ctx, deleteDetails); err != nil {
return err
if err = appDetailRepo.BatchDelete(ctx, deleteDetails); err != nil {
return
}
}
for _, u := range updateDetails {
if err := appDetailRepo.Update(ctx, u); err != nil {
return err
if err = appDetailRepo.Update(ctx, u); err != nil {
return
}
}
if len(oldAppIds) > 0 {
if err := appTagRepo.DeleteByAppIds(ctx, oldAppIds); err != nil {
return err
if err = appTagRepo.DeleteByAppIds(ctx, oldAppIds); err != nil {
return
}
}
if len(appTags) > 0 {
if err := appTagRepo.BatchCreate(ctx, appTags); err != nil {
return err
if err = appTagRepo.BatchCreate(ctx, appTags); err != nil {
return
}
}
tx.Commit()
if err := NewISettingService().Update("AppStoreLastModified", strconv.Itoa(list.LastModified)); err != nil {
return err
}
return nil
return
}
+138 -52
View File
@@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/backend/i18n"
"math"
"os"
"path"
@@ -12,6 +11,8 @@ import (
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"gopkg.in/yaml.v3"
@@ -40,9 +41,9 @@ type AppInstallService struct {
type IAppInstallService interface {
Page(req request.AppInstalledSearch) (int64, []response.AppInstalledDTO, error)
CheckExist(key string) (*response.AppInstalledCheck, error)
LoadPort(key string) (int64, error)
LoadConnInfo(key string) (response.DatabaseConn, error)
CheckExist(req request.AppInstalledInfo) (*response.AppInstalledCheck, error)
LoadPort(req dto.OperationWithNameAndType) (int64, error)
LoadConnInfo(req dto.OperationWithNameAndType) (response.DatabaseConn, error)
SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstalledDTO, error)
Operate(req request.AppInstalledOperate) error
Update(req request.AppInstalledUpdate) error
@@ -52,16 +53,35 @@ type IAppInstallService interface {
GetUpdateVersions(installId uint) ([]dto.AppVersion, error)
GetParams(id uint) (*response.AppConfig, error)
ChangeAppPort(req request.PortUpdate) error
GetDefaultConfigByKey(key string) (string, error)
GetDefaultConfigByKey(key, name string) (string, error)
DeleteCheck(installId uint) ([]dto.AppResource, error)
GetInstallList() ([]dto.AppInstallInfo, error)
}
func NewIAppInstalledService() IAppInstallService {
return &AppInstallService{}
}
func (a *AppInstallService) GetInstallList() ([]dto.AppInstallInfo, error) {
var datas []dto.AppInstallInfo
appInstalls, err := appInstallRepo.ListBy()
if err != nil {
return nil, err
}
for _, install := range appInstalls {
datas = append(datas, dto.AppInstallInfo{ID: install.ID, Key: install.App.Key, Name: install.Name})
}
return datas, nil
}
func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []response.AppInstalledDTO, error) {
var opts []repo.DBOption
var (
opts []repo.DBOption
total int64
installs []model.AppInstall
err error
)
if req.Name != "" {
opts = append(opts, commonRepo.WithLikeName(req.Name))
@@ -87,36 +107,53 @@ func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []respo
opts = append(opts, appInstallRepo.WithAppIdsIn(appIds))
}
total, installs, err := appInstallRepo.Page(req.Page, req.PageSize, opts...)
if err != nil {
return 0, nil, err
if req.Update {
installs, err = appInstallRepo.ListBy(opts...)
if err != nil {
return 0, nil, err
}
} else {
total, installs, err = appInstallRepo.Page(req.Page, req.PageSize, opts...)
if err != nil {
return 0, nil, err
}
}
installDTOs, err := handleInstalled(installs, req.Update)
if err != nil {
return 0, nil, err
}
if req.Update {
total = int64(len(installDTOs))
}
return total, installDTOs, nil
}
func (a *AppInstallService) CheckExist(key string) (*response.AppInstalledCheck, error) {
func (a *AppInstallService) CheckExist(req request.AppInstalledInfo) (*response.AppInstalledCheck, error) {
res := &response.AppInstalledCheck{
IsExist: false,
}
app, err := appRepo.GetFirst(appRepo.WithKey(key))
app, err := appRepo.GetFirst(appRepo.WithKey(req.Key))
if err != nil {
return res, nil
}
res.App = app.Name
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithAppId(app.ID))
var appInstall model.AppInstall
if len(req.Name) == 0 {
appInstall, _ = appInstallRepo.GetFirst(appInstallRepo.WithAppId(app.ID))
} else {
appInstall, _ = appInstallRepo.GetFirst(appInstallRepo.WithAppId(app.ID), commonRepo.WithByName(req.Name))
}
if reflect.DeepEqual(appInstall, model.AppInstall{}) {
return res, nil
}
if err := syncById(appInstall.ID); err != nil {
if err = syncByID(appInstall.ID); err != nil {
return nil, err
}
appInstall, _ = appInstallRepo.GetFirst(commonRepo.WithByID(appInstall.ID))
res.ContainerName = appInstall.ContainerName
res.Name = appInstall.Name
@@ -125,22 +162,24 @@ func (a *AppInstallService) CheckExist(key string) (*response.AppInstalledCheck,
res.Status = appInstall.Status
res.AppInstallID = appInstall.ID
res.IsExist = true
res.InstallPath = path.Join(constant.AppInstallDir, app.Key, appInstall.Name)
res.InstallPath = path.Join(constant.AppInstallDir, appInstall.App.Key, appInstall.Name)
res.HttpPort = appInstall.HttpPort
res.HttpsPort = appInstall.HttpsPort
return res, nil
}
func (a *AppInstallService) LoadPort(key string) (int64, error) {
app, err := appInstallRepo.LoadBaseInfo(key, "")
func (a *AppInstallService) LoadPort(req dto.OperationWithNameAndType) (int64, error) {
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil {
return int64(0), nil
}
return app.Port, nil
}
func (a *AppInstallService) LoadConnInfo(key string) (response.DatabaseConn, error) {
func (a *AppInstallService) LoadConnInfo(req dto.OperationWithNameAndType) (response.DatabaseConn, error) {
var data response.DatabaseConn
app, err := appInstallRepo.LoadBaseInfo(key, "")
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil {
return data, nil
}
@@ -200,28 +239,28 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
if err != nil {
return handleErr(install, err, out)
}
return syncById(install.ID)
return syncByID(install.ID)
case constant.Stop:
out, err := compose.Stop(dockerComposePath)
if err != nil {
return handleErr(install, err, out)
}
return syncById(install.ID)
return syncByID(install.ID)
case constant.Restart:
out, err := compose.Restart(dockerComposePath)
if err != nil {
return handleErr(install, err, out)
}
return syncById(install.ID)
return syncByID(install.ID)
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 syncByID(install.ID)
case constant.Upgrade:
return upgradeInstall(install.ID, req.DetailId)
return upgradeInstall(install.ID, req.DetailId, req.Backup)
default:
return errors.New("operate not support")
}
@@ -378,7 +417,7 @@ func (a *AppInstallService) SyncAll(systemInit bool) error {
continue
}
if !systemInit {
if err := syncById(i.ID); err != nil {
if err := syncByID(i.ID); err != nil {
global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error())
}
}
@@ -387,25 +426,50 @@ func (a *AppInstallService) SyncAll(systemInit bool) error {
}
func (a *AppInstallService) GetServices(key string) ([]response.AppService, error) {
app, err := appRepo.GetFirst(appRepo.WithKey(key))
if err != nil {
return nil, err
}
installs, err := appInstallRepo.ListBy(appInstallRepo.WithAppId(app.ID), appInstallRepo.WithStatus(constant.Running))
if err != nil {
return nil, err
}
var res []response.AppService
for _, install := range installs {
paramMap := make(map[string]string)
if install.Param != "" {
_ = json.Unmarshal([]byte(install.Param), &paramMap)
if DatabaseKeys[key] > 0 {
dbs, _ := databaseRepo.GetList(databaseRepo.WithByFrom("local"), commonRepo.WithByType(key))
if len(dbs) == 0 {
return res, nil
}
for _, db := range dbs {
service := response.AppService{
Label: db.Name,
Value: db.Name,
}
if db.AppInstallID > 0 {
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(db.AppInstallID))
if err != nil {
return nil, err
}
paramMap := make(map[string]string)
if install.Param != "" {
_ = json.Unmarshal([]byte(install.Param), &paramMap)
}
service.Config = paramMap
}
res = append(res, service)
}
} else {
app, err := appRepo.GetFirst(appRepo.WithKey(key))
if err != nil {
return nil, err
}
installs, err := appInstallRepo.ListBy(appInstallRepo.WithAppId(app.ID), appInstallRepo.WithStatus(constant.Running))
if err != nil {
return nil, err
}
for _, install := range installs {
paramMap := make(map[string]string)
if install.Param != "" {
_ = json.Unmarshal([]byte(install.Param), &paramMap)
}
res = append(res, response.AppService{
Label: install.Name,
Value: install.ServiceName,
Config: paramMap,
})
}
res = append(res, response.AppService{
Label: install.Name,
Value: install.ServiceName,
Config: paramMap,
})
}
return res, nil
}
@@ -508,22 +572,22 @@ func (a *AppInstallService) DeleteCheck(installId uint) ([]dto.AppResource, erro
return res, nil
}
func (a *AppInstallService) GetDefaultConfigByKey(key string) (string, error) {
appInstall, err := getAppInstallByKey(key)
func (a *AppInstallService) GetDefaultConfigByKey(key, name string) (string, error) {
baseInfo, err := appInstallRepo.LoadBaseInfo(key, name)
if err != nil {
return "", err
}
fileOp := files.NewFileOp()
filePath := path.Join(constant.AppResourceDir, "remote", appInstall.App.Key, appInstall.Version, "conf")
filePath := path.Join(constant.AppResourceDir, "remote", baseInfo.Key, baseInfo.Version, "conf")
if !fileOp.Stat(filePath) {
filePath = path.Join(constant.AppResourceDir, appInstall.App.Key, "versions", appInstall.Version, "conf")
filePath = path.Join(constant.AppResourceDir, baseInfo.Key, "versions", baseInfo.Version, "conf")
}
if !fileOp.Stat(filePath) {
return "", buserr.New(constant.ErrPathNotFound)
}
if key == constant.AppMysql {
if key == constant.AppMysql || key == constant.AppMariaDB {
filePath = path.Join(filePath, "my.cnf")
}
if key == constant.AppRedis {
@@ -554,10 +618,10 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
if err != nil {
return nil, err
}
if err := json.Unmarshal([]byte(detail.Params), &appForm); err != nil {
if err = json.Unmarshal([]byte(detail.Params), &appForm); err != nil {
return nil, err
}
if err := json.Unmarshal([]byte(install.Env), &envs); err != nil {
if err = json.Unmarshal([]byte(install.Env), &envs); err != nil {
return nil, err
}
for _, form := range appForm.FormFields {
@@ -587,18 +651,32 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
appParam.Values = form.Values
}
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,
})
}
}
config := getAppCommonConfig(envs)
config.DockerCompose = install.DockerCompose
res.Params = params
if config.ContainerName == "" {
config.ContainerName = install.ContainerName
}
res.AppContainerConfig = config
return &res, nil
}
func syncById(installId uint) error {
appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(installId))
func syncByID(installID uint) error {
appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(installID))
if err != nil {
return err
}
@@ -702,7 +780,11 @@ func updateInstallInfoInDB(appKey, appName, param string, isRestart bool, value
envKey := ""
switch param {
case "password":
envKey = "PANEL_DB_ROOT_PASSWORD="
if appKey == "mysql" || appKey == "mariadb" {
envKey = "PANEL_DB_ROOT_PASSWORD="
} else {
envKey = "PANEL_REDIS_ROOT_PASSWORD="
}
case "port":
envKey = "PANEL_APP_PORT_HTTP="
case "user-password":
@@ -731,6 +813,10 @@ func updateInstallInfoInDB(appKey, appName, param string, isRestart bool, value
if param == "password" {
oldVal = fmt.Sprintf("\"PANEL_DB_ROOT_PASSWORD\":\"%v\"", appInstall.Password)
newVal = fmt.Sprintf("\"PANEL_DB_ROOT_PASSWORD\":\"%v\"", value)
if appKey == "redis" {
oldVal = fmt.Sprintf("\"PANEL_REDIS_ROOT_PASSWORD\":\"%v\"", appInstall.Password)
newVal = fmt.Sprintf("\"PANEL_REDIS_ROOT_PASSWORD\":\"%v\"", value)
}
_ = appInstallRepo.BatchUpdateBy(map[string]interface{}{
"param": strings.ReplaceAll(appInstall.Param, oldVal, newVal),
"env": strings.ReplaceAll(appInstall.Env, oldVal, newVal),
+246 -73
View File
@@ -5,11 +5,6 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/subosito/gotenv"
"gopkg.in/yaml.v3"
"math"
"net/http"
"os"
@@ -20,6 +15,14 @@ import (
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/subosito/gotenv"
"gopkg.in/yaml.v3"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/utils/env"
@@ -79,23 +82,84 @@ func checkPort(key string, params map[string]interface{}) (int, error) {
return 0, nil
}
var DatabaseKeys = map[string]uint{
"mysql": 3306,
"mariadb": 3306,
"postgresql": 5432,
"mongodb": 27017,
"redis": 6379,
"memcached": 11211,
}
func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall, params map[string]interface{}) error {
var dbConfig dto.AppDatabase
if app.Type == "runtime" {
var authParam dto.AuthParam
paramByte, err := json.Marshal(params)
if app.Type == "runtime" && DatabaseKeys[app.Key] > 0 {
database := &model.Database{
AppInstallID: appInstall.ID,
Name: appInstall.Name,
Type: app.Key,
Version: appInstall.Version,
From: "local",
Address: appInstall.ServiceName,
Port: DatabaseKeys[app.Key],
}
detail, err := appDetailRepo.GetFirst(commonRepo.WithByID(appInstall.AppDetailId))
if err != nil {
return err
}
if err := json.Unmarshal(paramByte, &authParam); err != nil {
formFields := &dto.AppForm{}
if err := json.Unmarshal([]byte(detail.Params), formFields); err != nil {
return err
}
if authParam.RootPassword != "" {
authByte, err := json.Marshal(authParam)
if err != nil {
return err
for _, form := range formFields.FormFields {
if form.EnvKey == "PANEL_APP_PORT_HTTP" {
portFloat, ok := form.Default.(float64)
if ok {
database.Port = uint(int(portFloat))
}
break
}
appInstall.Param = string(authByte)
}
switch app.Key {
case "mysql", "mariadb", "postgresql", "mongodb":
if password, ok := params["PANEL_DB_ROOT_PASSWORD"]; ok {
if password != "" {
authParam := dto.AuthParam{
RootPassword: password.(string),
}
authByte, err := json.Marshal(authParam)
if err != nil {
return err
}
appInstall.Param = string(authByte)
database.Password = password.(string)
if app.Key == "mysql" || app.Key == "mariadb" {
database.Username = "root"
}
if rootUser, ok := params["PANEL_DB_ROOT_USER"]; ok {
database.Username = rootUser.(string)
}
}
}
case "redis":
if password, ok := params["PANEL_REDIS_ROOT_PASSWORD"]; ok {
if password != "" {
authParam := dto.RedisAuthParam{
RootPassword: password.(string),
}
authByte, err := json.Marshal(authParam)
if err != nil {
return err
}
appInstall.Param = string(authByte)
}
database.Password = password.(string)
}
}
if err := databaseRepo.Create(ctx, database); err != nil {
return err
}
}
if app.Type == "website" || app.Type == "tool" {
@@ -103,7 +167,7 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall
if err != nil {
return err
}
if err := json.Unmarshal(paramByte, &dbConfig); err != nil {
if err = json.Unmarshal(paramByte, &dbConfig); err != nil {
return err
}
}
@@ -116,7 +180,7 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall
var resourceId uint
if dbConfig.DbName != "" && dbConfig.DbUser != "" && dbConfig.Password != "" {
iMysqlRepo := repo.NewIMysqlRepo()
oldMysqlDb, _ := iMysqlRepo.Get(commonRepo.WithByName(dbConfig.DbName))
oldMysqlDb, _ := iMysqlRepo.Get(commonRepo.WithByName(dbConfig.DbName), iMysqlRepo.WithByFrom(constant.ResourceLocal))
resourceId = oldMysqlDb.ID
if oldMysqlDb.ID > 0 {
if oldMysqlDb.Username != dbConfig.DbUser || oldMysqlDb.Password != dbConfig.Password {
@@ -126,9 +190,11 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall
var createMysql dto.MysqlDBCreate
createMysql.Name = dbConfig.DbName
createMysql.Username = dbConfig.DbUser
createMysql.Database = dbInstall.Name
createMysql.Format = "utf8mb4"
createMysql.Permission = "%"
createMysql.Password = dbConfig.Password
createMysql.From = "local"
mysqldb, err := NewIMysqlService().Create(ctx, createMysql)
if err != nil {
return err
@@ -158,7 +224,7 @@ func handleAppInstallErr(ctx context.Context, install *model.AppInstall) error {
return err
}
}
if err := deleteLink(ctx, install, true, true); err != nil {
if err := deleteLink(ctx, install, true, true, true); err != nil {
return err
}
return nil
@@ -173,27 +239,40 @@ func deleteAppInstall(install model.AppInstall, deleteBackup bool, forceDelete b
if err != nil && !forceDelete {
return handleErr(install, err, out)
}
if err = runScript(&install, "uninstall"); err != nil {
_, _ = compose.Up(install.GetComposePath())
return err
}
}
tx, ctx := helper.GetTxAndContext()
defer tx.Rollback()
if err := appInstallRepo.Delete(ctx, install); err != nil {
return err
}
if err := deleteLink(ctx, &install, deleteDB, forceDelete); err != nil && !forceDelete {
if err := deleteLink(ctx, &install, deleteDB, forceDelete, deleteBackup); err != nil && !forceDelete {
return err
}
_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType("app"), commonRepo.WithByName(install.App.Key), backupRepo.WithByDetailName(install.Name))
_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType(install.App.Key))
if install.App.Key == constant.AppMysql {
_ = mysqlRepo.DeleteAll(ctx)
if DatabaseKeys[install.App.Key] > 0 {
_ = databaseRepo.Delete(ctx, databaseRepo.WithAppInstallID(install.ID))
}
uploadDir := fmt.Sprintf("%s/1panel/uploads/app/%s/%s", global.CONF.System.BaseDir, install.App.Key, install.Name)
switch install.App.Key {
case constant.AppOpenresty:
_ = websiteRepo.DeleteAll(ctx)
_ = websiteDomainRepo.DeleteAll(ctx)
case constant.AppMysql, constant.AppMariaDB:
_ = mysqlRepo.Delete(ctx, mysqlRepo.WithByMysqlName(install.Name))
}
_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType("app"), commonRepo.WithByName(install.App.Key), backupRepo.WithByDetailName(install.Name))
uploadDir := path.Join(global.CONF.System.BaseDir, fmt.Sprintf("1panel/uploads/app/%s/%s", install.App.Key, install.Name))
if _, err := os.Stat(uploadDir); err == nil {
_ = os.RemoveAll(uploadDir)
}
if deleteBackup {
localDir, _ := loadLocalDir()
backupDir := fmt.Sprintf("%s/app/%s/%s", localDir, install.App.Key, install.Name)
backupDir := path.Join(localDir, fmt.Sprintf("app/%s/%s", install.App.Key, install.Name))
if _, err := os.Stat(backupDir); err == nil {
_ = os.RemoveAll(backupDir)
}
@@ -204,21 +283,25 @@ func deleteAppInstall(install model.AppInstall, deleteBackup bool, forceDelete b
return nil
}
func deleteLink(ctx context.Context, install *model.AppInstall, deleteDB bool, forceDelete bool) error {
func deleteLink(ctx context.Context, install *model.AppInstall, deleteDB bool, forceDelete bool, deleteBackup bool) error {
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
if len(resources) == 0 {
return nil
}
for _, re := range resources {
mysqlService := NewIMysqlService()
if re.Key == "mysql" && deleteDB {
if (re.Key == constant.AppMysql || re.Key == constant.AppMariaDB) && deleteDB {
database, _ := mysqlRepo.Get(commonRepo.WithByID(re.ResourceId))
if reflect.DeepEqual(database, model.DatabaseMysql{}) {
continue
}
if err := mysqlService.Delete(ctx, dto.MysqlDBDelete{
ID: database.ID,
ForceDelete: forceDelete,
ID: database.ID,
ForceDelete: forceDelete,
DeleteBackup: deleteBackup,
Type: re.Key,
Database: database.MysqlName,
}); err != nil && !forceDelete {
return err
}
@@ -227,7 +310,7 @@ func deleteLink(ctx context.Context, install *model.AppInstall, deleteDB bool, f
return appInstallResourceRepo.DeleteBy(ctx, appInstallResourceRepo.WithAppInstallId(install.ID))
}
func upgradeInstall(installId uint, detailId uint) error {
func upgradeInstall(installId uint, detailId uint, backup bool) error {
install, err := appInstallRepo.GetFirst(commonRepo.WithByID(installId))
if err != nil {
return err
@@ -239,13 +322,14 @@ func upgradeInstall(installId uint, detailId uint) error {
if install.Version == detail.Version {
return errors.New("two version is same")
}
if err := NewIBackupService().AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name}); err != nil {
return err
}
install.Status = constant.Upgrading
go func() {
if backup {
if err = NewIBackupService().AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name}); err != nil {
global.LOG.Errorf(i18n.GetMsgWithMap("ErrAppBackup", map[string]interface{}{"name": install.Name, "err": err.Error()}))
}
}
var upErr error
defer func() {
if upErr != nil {
@@ -268,15 +352,19 @@ func upgradeInstall(installId uint, detailId uint) error {
detailDir = path.Join(constant.ResourceDir, "apps", "local", strings.TrimPrefix(install.App.Key, "local"), detail.Version)
}
cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rf %s/* %s", detailDir, install.GetPath()))
stdout, err := cmd.CombinedOutput()
if err != nil {
if stdout != nil {
upErr = errors.New(string(stdout))
return
}
upErr = err
return
command := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rn %s/* %s || true", detailDir, install.GetPath()))
stdout, _ := command.CombinedOutput()
if stdout != nil {
global.LOG.Infof("upgrade app [%s] [%s] cp file log : %s ", install.App.Key, install.Name, string(stdout))
}
fileOp := files.NewFileOp()
sourceScripts := path.Join(detailDir, "scripts")
if fileOp.Stat(sourceScripts) {
dstScripts := path.Join(install.GetPath(), "scripts")
_ = fileOp.DeleteDir(dstScripts)
_ = fileOp.CreateDir(dstScripts, 0755)
scriptCmd := exec.Command("cp", "-rf", sourceScripts+"/.", dstScripts+"/")
_, _ = scriptCmd.CombinedOutput()
}
composeMap := make(map[string]interface{})
@@ -289,20 +377,21 @@ func upgradeInstall(installId uint, detailId uint) error {
return
}
servicesMap := value.(map[string]interface{})
index := 0
oldServiceName := ""
for k := range servicesMap {
oldServiceName = k
index++
if index > 0 {
break
if len(servicesMap) == 1 {
index := 0
oldServiceName := ""
for k := range servicesMap {
oldServiceName = k
index++
if index > 0 {
break
}
}
servicesMap[install.ServiceName] = servicesMap[oldServiceName]
if install.ServiceName != oldServiceName {
delete(servicesMap, oldServiceName)
}
}
servicesMap[install.ServiceName] = servicesMap[oldServiceName]
if install.ServiceName != oldServiceName {
delete(servicesMap, oldServiceName)
}
envs := make(map[string]interface{})
if upErr = json.Unmarshal([]byte(install.Env), &envs); upErr != nil {
return
@@ -330,6 +419,24 @@ func upgradeInstall(installId uint, detailId uint) error {
install.Version = detail.Version
install.AppDetailId = detailId
images, err := getImages(install)
if err != nil {
upErr = err
return
}
dockerCli, err := composeV2.NewClient()
if err != nil {
upErr = err
return
}
for _, image := range images {
if err = dockerCli.PullImage(image, true); err != nil {
upErr = buserr.WithNameAndErr("ErrDockerPullImage", "", err)
return
}
}
if out, err := compose.Down(install.GetComposePath()); err != nil {
if out != "" {
upErr = errors.New(out)
@@ -337,12 +444,16 @@ func upgradeInstall(installId uint, detailId uint) error {
}
return
}
fileOp := files.NewFileOp()
envParams := make(map[string]string, len(envs))
handleMap(envs, envParams)
if err = env.Write(envParams, install.GetEnvPath()); err != nil {
return
}
if err = runScript(&install, "upgrade"); err != nil {
return
}
if upErr = fileOp.WriteFile(install.GetComposePath(), strings.NewReader(install.DockerCompose), 0775); upErr != nil {
return
}
@@ -381,9 +492,32 @@ func getContainerNames(install model.AppInstall) ([]string, error) {
for k := range containerMap {
containerNames = append(containerNames, k)
}
if len(containerNames) == 0 {
containerNames = append(containerNames, install.ContainerName)
}
return containerNames, nil
}
func getImages(install model.AppInstall) ([]string, error) {
envStr, err := coverEnvJsonToStr(install.Env)
if err != nil {
return nil, err
}
project, err := composeV2.GetComposeProject(install.Name, install.GetPath(), []byte(install.DockerCompose), []byte(envStr), true)
if err != nil {
return nil, err
}
imagesMap := make(map[string]struct{})
for _, service := range project.AllServices() {
imagesMap[service.Image] = struct{}{}
}
var images []string
for k := range imagesMap {
images = append(images, k)
}
return images, nil
}
func coverEnvJsonToStr(envJson string) (string, error) {
envMap := make(map[string]interface{})
_ = json.Unmarshal([]byte(envJson), &envMap)
@@ -423,6 +557,8 @@ func handleMap(params map[string]interface{}, envParams map[string]string) {
envParams[k] = t
case float64:
envParams[k] = strconv.FormatFloat(t, 'f', -1, 32)
case uint:
envParams[k] = strconv.Itoa(int(t))
default:
envParams[k] = t.(string)
}
@@ -444,7 +580,7 @@ func downloadApp(app model.App, appDetail model.AppDetail, appInstall *model.App
_ = fileOp.CreateDir(appVersionDir, 0755)
}
global.LOG.Infof("download app[%s] from %s", app.Name, appDetail.DownloadUrl)
filePath := path.Join(appVersionDir, appDetail.Version+".tar.gz")
filePath := path.Join(appVersionDir, app.Key+"-"+appDetail.Version+".tar.gz")
defer func() {
if err != nil {
@@ -459,7 +595,7 @@ func downloadApp(app model.App, appDetail model.AppDetail, appInstall *model.App
global.LOG.Errorf("download app[%s] error %v", app.Name, err)
return
}
if err = fileOp.Decompress(filePath, appVersionDir, files.TarGz); err != nil {
if err = fileOp.Decompress(filePath, appResourceDir, files.TarGz); err != nil {
global.LOG.Errorf("decompress app[%s] error %v", app.Name, err)
return
}
@@ -522,13 +658,28 @@ func copyData(app model.App, appDetail model.AppDetail, appInstall *model.AppIns
return
}
// 处理文件夹权限等问题
func upAppPre(app model.App, appInstall *model.AppInstall) error {
if app.Key == "nexus" {
dataPath := path.Join(appInstall.GetPath(), "data")
if err := files.NewFileOp().Chown(dataPath, 200, 0); err != nil {
return err
func runScript(appInstall *model.AppInstall, operate string) error {
workDir := appInstall.GetPath()
scriptPath := ""
switch operate {
case "init":
scriptPath = path.Join(workDir, "scripts", "init.sh")
case "upgrade":
scriptPath = path.Join(workDir, "scripts", "upgrade.sh")
case "uninstall":
scriptPath = path.Join(workDir, "scripts", "uninstall.sh")
}
if !files.NewFileOp().Stat(scriptPath) {
return nil
}
out, err := cmd.ExecScript(scriptPath, workDir)
if err != nil {
if out != "" {
errMsg := fmt.Sprintf("run script %s error %s", scriptPath, out)
global.LOG.Error(errMsg)
return errors.New(errMsg)
}
return err
}
return nil
}
@@ -604,16 +755,24 @@ func upApp(appInstall *model.AppInstall) {
}
func rebuildApp(appInstall model.AppInstall) error {
dockerComposePath := appInstall.GetComposePath()
out, err := compose.Down(dockerComposePath)
if err != nil {
return handleErr(appInstall, err, out)
}
out, err = compose.Up(dockerComposePath)
if err != nil {
return handleErr(appInstall, err, out)
}
return syncById(appInstall.ID)
appInstall.Status = constant.Rebuilding
_ = appInstallRepo.Save(context.Background(), &appInstall)
go func() {
dockerComposePath := appInstall.GetComposePath()
out, err := compose.Down(dockerComposePath)
if err != nil {
_ = handleErr(appInstall, err, out)
return
}
out, err = compose.Up(dockerComposePath)
if err != nil {
_ = handleErr(appInstall, err, out)
return
}
appInstall.Status = constant.Running
_ = appInstallRepo.Save(context.Background(), &appInstall)
}()
return nil
}
func getAppDetails(details []model.AppDetail, versions []dto.AppConfigVersion) map[string]model.AppDetail {
@@ -784,6 +943,10 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]respons
}
versions = common.GetSortedVersions(versions)
if len(versions) == 0 {
if !updated {
installDTO.CanUpdate = false
res = append(res, installDTO)
}
continue
}
lastVersion := versions[0]
@@ -815,6 +978,16 @@ func getAppInstallByKey(key string) (model.AppInstall, error) {
return appInstall, nil
}
func getAppInstallPort(key string) (httpPort, httpsPort int, err error) {
install, err := getAppInstallByKey(key)
if err != nil {
return
}
httpPort = install.HttpPort
httpsPort = install.HttpsPort
return
}
func updateToolApp(installed *model.AppInstall) {
tooKey, ok := dto.AppToolMap[installed.App.Key]
if !ok {
+22 -6
View File
@@ -4,6 +4,7 @@ import (
"strconv"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"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/encrypt"
@@ -19,16 +20,16 @@ type AuthService struct{}
type IAuthService interface {
CheckIsSafety(code string) (string, error)
VerifyCode(code string) (bool, error)
Login(c *gin.Context, info dto.Login) (*dto.UserLoginInfo, error)
Login(c *gin.Context, info dto.Login, entrance string) (*dto.UserLoginInfo, error)
LogOut(c *gin.Context) error
MFALogin(c *gin.Context, info dto.MFALogin) (*dto.UserLoginInfo, error)
MFALogin(c *gin.Context, info dto.MFALogin, entrance string) (*dto.UserLoginInfo, error)
}
func NewIAuthService() IAuthService {
return &AuthService{}
}
func (u *AuthService) Login(c *gin.Context, info dto.Login) (*dto.UserLoginInfo, error) {
func (u *AuthService) Login(c *gin.Context, info dto.Login, entrance string) (*dto.UserLoginInfo, error) {
nameSetting, err := settingRepo.Get(settingRepo.WithByKey("UserName"))
if err != nil {
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
@@ -44,18 +45,27 @@ func (u *AuthService) Login(c *gin.Context, info dto.Login) (*dto.UserLoginInfo,
if info.Password != pass || nameSetting.Value != info.Name {
return nil, constant.ErrAuth
}
entranceSetting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
if err != nil {
return nil, err
}
if len(entranceSetting.Value) != 0 && entranceSetting.Value != entrance {
return nil, buserr.New(constant.ErrEntrance)
}
mfa, err := settingRepo.Get(settingRepo.WithByKey("MFAStatus"))
if err != nil {
return nil, err
}
if err = settingRepo.Update("Language", info.Language); err != nil {
return nil, err
}
if mfa.Value == "enable" {
return &dto.UserLoginInfo{Name: nameSetting.Value, MfaStatus: mfa.Value}, nil
}
return u.generateSession(c, info.Name, info.AuthMethod)
}
func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin) (*dto.UserLoginInfo, error) {
func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin, entrance string) (*dto.UserLoginInfo, error) {
nameSetting, err := settingRepo.Get(settingRepo.WithByKey("UserName"))
if err != nil {
return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error())
@@ -71,7 +81,13 @@ func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin) (*dto.UserLogi
if info.Password != pass || nameSetting.Value != info.Name {
return nil, constant.ErrAuth
}
entranceSetting, err := settingRepo.Get(settingRepo.WithByKey("SecurityEntrance"))
if err != nil {
return nil, err
}
if len(entranceSetting.Value) != 0 && entranceSetting.Value != entrance {
return nil, buserr.New(constant.ErrEntrance)
}
mfaSecret, err := settingRepo.Get(settingRepo.WithByKey("MFASecret"))
if err != nil {
return nil, err
+17 -3
View File
@@ -37,7 +37,7 @@ type IBackupService interface {
BatchDeleteRecord(ids []uint) error
NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error)
ListFiles(req dto.BackupSearchFile) ([]interface{}, error)
ListFiles(req dto.BackupSearchFile) ([]string, error)
MysqlBackup(db dto.CommonBackup) error
MysqlRecover(db dto.CommonRecover) error
@@ -273,7 +273,7 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
return nil
}
func (u *BackupService) ListFiles(req dto.BackupSearchFile) ([]interface{}, error) {
func (u *BackupService) ListFiles(req dto.BackupSearchFile) ([]string, error) {
backup, err := backupRepo.Get(backupRepo.WithByType(req.Type))
if err != nil {
return nil, err
@@ -282,7 +282,21 @@ func (u *BackupService) ListFiles(req dto.BackupSearchFile) ([]interface{}, erro
if err != nil {
return nil, err
}
return client.ListObjects("system_snapshot/")
prefix := "system_snapshot"
if len(backup.BackupPath) != 0 {
prefix = path.Join(strings.TrimPrefix(backup.BackupPath, "/"), prefix)
}
files, err := client.ListObjects(prefix)
if err != nil {
return nil, err
}
var datas []string
for _, file := range files {
if len(file) != 0 {
datas = append(datas, path.Base(file))
}
}
return datas, nil
}
func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error) {
+61 -38
View File
@@ -34,7 +34,7 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) error {
}
timeNow := time.Now().Format("20060102150405")
backupDir := fmt.Sprintf("%s/app/%s/%s", localDir, req.Name, req.DetailName)
backupDir := path.Join(localDir, fmt.Sprintf("app/%s/%s", req.Name, req.DetailName))
fileName := fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow)
if err := handleAppBackup(&install, backupDir, fileName); err != nil {
@@ -104,18 +104,16 @@ func handleAppBackup(install *model.AppInstall, backupDir, fileName string) erro
return err
}
resource, _ := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(install.ID))
if resource.ID != 0 && resource.ResourceId != 0 {
mysqlInfo, err := appInstallRepo.LoadBaseInfo(constant.AppMysql, "")
if err != nil {
return err
}
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
if err != nil {
return err
}
if err := handleMysqlBackup(mysqlInfo, tmpDir, db.Name, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil {
return err
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
for _, resource := range resources {
if resource.Key == "mysql" || resource.Key == "mariadb" {
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
if err != nil {
return err
}
if err := handleMysqlBackup(db.MysqlName, db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil {
return err
}
}
}
@@ -153,7 +151,7 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
}
if !isRollback {
rollbackFile := fmt.Sprintf("%s/original/app/%s_%s.tar.gz", global.CONF.System.BaseDir, install.Name, time.Now().Format("20060102150405"))
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("app/%s_%s.tar.gz", install.Name, time.Now().Format("20060102150405")))
if err := handleAppBackup(install, path.Dir(rollbackFile), path.Base(rollbackFile)); err != nil {
return fmt.Errorf("backup app %s for rollback before recover failed, err: %v", install.Name, err)
}
@@ -173,41 +171,62 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
}
newEnvFile := ""
resource, _ := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(install.ID))
if resource.ID != 0 && install.App.Key != "mysql" {
mysqlInfo, err := appInstallRepo.LoadBaseInfo(resource.Key, "")
if err != nil {
return err
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
for _, resource := range resources {
if resource.From != "local" {
continue
}
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
resourceApp, err := appInstallRepo.GetFirst(commonRepo.WithByID(resource.LinkId))
if err != nil {
return err
}
if resource.Key == "mysql" || resource.Key == "mariadb" {
mysqlInfo, err := appInstallRepo.LoadBaseInfo(resource.Key, resourceApp.Name)
if err != nil {
return err
}
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
if err != nil {
return err
}
newDB, envMap, err := reCreateDB(db.ID, oldInstall.Env)
if err != nil {
return err
}
oldHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", envMap["PANEL_DB_HOST"].(string))
newHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", mysqlInfo.ServiceName)
oldInstall.Env = strings.ReplaceAll(oldInstall.Env, oldHost, newHost)
envMap["PANEL_DB_HOST"] = mysqlInfo.ServiceName
newEnvFile, err = coverEnvJsonToStr(oldInstall.Env)
if err != nil {
return err
}
_ = appInstallResourceRepo.BatchUpdateBy(map[string]interface{}{"resource_id": newDB.ID}, commonRepo.WithByID(resource.ID))
newDB, envMap, err := reCreateDB(db.ID, resourceApp, oldInstall.Env)
if err != nil {
return err
}
oldHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", envMap["PANEL_DB_HOST"].(string))
newHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", mysqlInfo.ServiceName)
oldInstall.Env = strings.ReplaceAll(oldInstall.Env, oldHost, newHost)
envMap["PANEL_DB_HOST"] = mysqlInfo.ServiceName
newEnvFile, err = coverEnvJsonToStr(oldInstall.Env)
if err != nil {
return err
}
_ = appInstallResourceRepo.BatchUpdateBy(map[string]interface{}{"resource_id": newDB.ID}, commonRepo.WithByID(resource.ID))
if err := handleMysqlRecover(mysqlInfo, tmpPath, newDB.Name, fmt.Sprintf("%s.sql.gz", install.Name), true); err != nil {
global.LOG.Errorf("handle recover from sql.gz failed, err: %v", err)
return err
if err := handleMysqlRecover(dto.CommonRecover{
Name: newDB.MysqlName,
DetailName: newDB.Name,
File: fmt.Sprintf("%s/%s.sql.gz", tmpPath, install.Name),
}, true); err != nil {
global.LOG.Errorf("handle recover from sql.gz failed, err: %v", err)
return err
}
}
}
appDir := install.GetPath()
backPath := fmt.Sprintf("%s_bak", appDir)
_ = fileOp.Rename(appDir, backPath)
_ = fileOp.CreateDir(appDir, 0755)
if err := handleUnTar(tmpPath+"/app.tar.gz", fmt.Sprintf("%s/%s", constant.AppInstallDir, install.App.Key)); err != nil {
global.LOG.Errorf("handle recover from app.tar.gz failed, err: %v", err)
_ = fileOp.DeleteDir(appDir)
_ = fileOp.Rename(backPath, appDir)
return err
}
_ = fileOp.DeleteDir(backPath)
if len(newEnvFile) != 0 {
envPath := fmt.Sprintf("%s/%s/%s/.env", constant.AppInstallDir, install.App.Key, install.Name)
@@ -223,18 +242,20 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
oldInstall.Status = constant.StatusRunning
oldInstall.AppId = install.AppId
oldInstall.AppDetailId = install.AppDetailId
oldInstall.App.ID = install.AppId
if err := appInstallRepo.Save(context.Background(), &oldInstall); err != nil {
global.LOG.Errorf("save db app install failed, err: %v", err)
return err
}
isOk = true
return nil
}
func reCreateDB(dbID uint, oldEnv string) (*model.DatabaseMysql, map[string]interface{}, error) {
func reCreateDB(dbID uint, app model.AppInstall, oldEnv string) (*model.DatabaseMysql, map[string]interface{}, error) {
mysqlService := NewIMysqlService()
ctx := context.Background()
_ = mysqlService.Delete(ctx, dto.MysqlDBDelete{ID: dbID, DeleteBackup: true, ForceDelete: true})
_ = mysqlService.Delete(ctx, dto.MysqlDBDelete{ID: dbID, Database: app.Name, Type: app.App.Key, DeleteBackup: false, ForceDelete: true})
envMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(oldEnv), &envMap); err != nil {
@@ -245,6 +266,8 @@ func reCreateDB(dbID uint, oldEnv string) (*model.DatabaseMysql, map[string]inte
oldPassword, _ := envMap["PANEL_DB_USER_PASSWORD"].(string)
createDB, err := mysqlService.Create(context.Background(), dto.MysqlDBCreate{
Name: oldName,
From: "local",
Database: app.Name,
Format: "utf8mb4",
Username: oldUser,
Password: oldPassword,
+73 -75
View File
@@ -1,10 +1,8 @@
package service
import (
"compress/gzip"
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
@@ -12,9 +10,9 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/1Panel-dev/1Panel/backend/utils/mysql/client"
"github.com/pkg/errors"
)
@@ -23,24 +21,22 @@ func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
if err != nil {
return err
}
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
if err != nil {
timeNow := time.Now().Format("20060102150405")
targetDir := path.Join(localDir, fmt.Sprintf("database/%s/%s/%s", req.Type, req.Name, req.DetailName))
fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow)
if err := handleMysqlBackup(req.Name, req.DetailName, targetDir, fileName); err != nil {
return err
}
timeNow := time.Now().Format("20060102150405")
backupDir := fmt.Sprintf("%s/database/mysql/%s/%s", localDir, req.Name, req.DetailName)
fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow)
if err := handleMysqlBackup(app, backupDir, req.DetailName, fileName); err != nil {
return err
}
record := &model.BackupRecord{
Type: "mysql",
Name: app.Name,
Type: req.Type,
Name: req.Name,
DetailName: req.DetailName,
Source: "LOCAL",
BackupType: "LOCAL",
FileDir: backupDir,
FileDir: targetDir,
FileName: fileName,
}
if err := backupRepo.CreateRecord(record); err != nil {
@@ -50,26 +46,13 @@ func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
}
func (u *BackupService) MysqlRecover(req dto.CommonRecover) error {
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
if err != nil {
return err
}
fileOp := files.NewFileOp()
if !fileOp.Stat(req.File) {
return errors.New(fmt.Sprintf("%s file is not exist", req.File))
}
global.LOG.Infof("recover database %s-%s from backup file %s", req.Name, req.DetailName, req.File)
if err := handleMysqlRecover(app, path.Dir(req.File), req.DetailName, path.Base(req.File), false); err != nil {
if err := handleMysqlRecover(req, false); err != nil {
return err
}
return nil
}
func (u *BackupService) MysqlRecoverByUpload(req dto.CommonRecover) error {
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
if err != nil {
return err
}
file := req.File
fileName := path.Base(req.File)
if strings.HasSuffix(fileName, ".tar.gz") {
@@ -106,77 +89,92 @@ func (u *BackupService) MysqlRecoverByUpload(req dto.CommonRecover) error {
}()
}
if err := handleMysqlRecover(app, path.Dir(file), req.DetailName, fileName, false); err != nil {
req.File = path.Dir(file) + "/" + fileName
if err := handleMysqlRecover(req, false); err != nil {
return err
}
global.LOG.Info("recover from uploads successful!")
return nil
}
func handleMysqlBackup(app *repo.RootInfo, backupDir, dbName, fileName string) error {
fileOp := files.NewFileOp()
if !fileOp.Stat(backupDir) {
if err := os.MkdirAll(backupDir, os.ModePerm); err != nil {
return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err)
}
func handleMysqlBackup(database, dbName, targetDir, fileName string) error {
dbInfo, err := mysqlRepo.Get(commonRepo.WithByName(dbName), mysqlRepo.WithByMysqlName(database))
if err != nil {
return err
}
cli, _, err := LoadMysqlClientByFrom(database)
if err != nil {
return err
}
outfile, _ := os.OpenFile(backupDir+"/"+fileName, os.O_RDWR|os.O_CREATE, 0755)
global.LOG.Infof("start to mysqldump | gzip > %s.gzip", backupDir+"/"+fileName)
cmd := exec.Command("docker", "exec", app.ContainerName, "mysqldump", "-uroot", "-p"+app.Password, dbName)
gzipCmd := exec.Command("gzip", "-cf")
gzipCmd.Stdin, _ = cmd.StdoutPipe()
gzipCmd.Stdout = outfile
_ = gzipCmd.Start()
_ = cmd.Run()
_ = gzipCmd.Wait()
backupInfo := client.BackupInfo{
Name: dbName,
Format: dbInfo.Format,
TargetDir: targetDir,
FileName: fileName,
Timeout: 300,
}
if err := cli.Backup(backupInfo); err != nil {
return err
}
return nil
}
func handleMysqlRecover(mysqlInfo *repo.RootInfo, recoverDir, dbName, fileName string, isRollback bool) error {
func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
isOk := false
fileOp := files.NewFileOp()
if !fileOp.Stat(req.File) {
return errors.New(fmt.Sprintf("%s file is not exist", req.File))
}
dbInfo, err := mysqlRepo.Get(commonRepo.WithByName(req.DetailName), mysqlRepo.WithByMysqlName(req.Name))
if err != nil {
return err
}
cli, _, err := LoadMysqlClientByFrom(req.Name)
if err != nil {
return err
}
if !isRollback {
rollbackFile := fmt.Sprintf("%s/original/database/%s_%s.sql.gz", global.CONF.System.BaseDir, mysqlInfo.Name, time.Now().Format("20060102150405"))
if err := handleMysqlBackup(mysqlInfo, path.Dir(rollbackFile), dbName, path.Base(rollbackFile)); err != nil {
return fmt.Errorf("backup mysql db %s for rollback before recover failed, err: %v", mysqlInfo.Name, err)
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,
Format: dbInfo.Format,
TargetDir: path.Dir(rollbackFile),
FileName: path.Base(rollbackFile),
Timeout: 300,
}); err != nil {
return fmt.Errorf("backup mysql db %s for rollback before recover failed, err: %v", req.DetailName, err)
}
defer func() {
if !isOk {
global.LOG.Info("recover failed, start to rollback now")
if err := handleMysqlRecover(mysqlInfo, path.Dir(rollbackFile), dbName, path.Base(rollbackFile), true); err != nil {
global.LOG.Errorf("rollback mysql db %s from %s failed, err: %v", dbName, rollbackFile, err)
return
if err := cli.Recover(client.RecoverInfo{
Name: req.DetailName,
Format: dbInfo.Format,
SourceFile: rollbackFile,
Timeout: 300,
}); err != nil {
global.LOG.Errorf("rollback mysql db %s from %s failed, err: %v", req.DetailName, rollbackFile, err)
}
global.LOG.Infof("rollback mysql db %s from %s successful", dbName, rollbackFile)
global.LOG.Infof("rollback mysql db %s from %s successful", req.DetailName, rollbackFile)
_ = os.RemoveAll(rollbackFile)
} else {
_ = os.RemoveAll(rollbackFile)
}
}()
}
file := recoverDir + "/" + fileName
fi, _ := os.Open(file)
defer fi.Close()
cmd := exec.Command("docker", "exec", "-i", mysqlInfo.ContainerName, "mysql", "-uroot", "-p"+mysqlInfo.Password, dbName)
if strings.HasSuffix(fileName, ".gz") {
gzipFile, err := os.Open(file)
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)
if err := cli.Recover(client.RecoverInfo{
Name: req.DetailName,
Format: dbInfo.Format,
SourceFile: req.File,
Timeout: 300,
}); err != nil {
return err
}
isOk = true
return nil
+2 -2
View File
@@ -43,7 +43,7 @@ func (u *BackupService) RedisBackup() error {
fileName = fmt.Sprintf("%s.tar.gz", timeNow)
}
}
backupDir := fmt.Sprintf("%s/database/redis/%s", localDir, redisInfo.Name)
backupDir := path.Join(localDir, fmt.Sprintf("database/redis/%s", redisInfo.Name))
if err := handleRedisBackup(redisInfo, backupDir, fileName); err != nil {
return err
}
@@ -143,7 +143,7 @@ func handleRedisRecover(redisInfo *repo.RootInfo, recoverFile string, isRollback
suffix = "tar.gz"
}
}
rollbackFile := fmt.Sprintf("%s/original/database/redis/%s_%s.%s", global.CONF.System.BaseDir, redisInfo.Name, time.Now().Format("20060102150405"), suffix)
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/redis/%s_%s.%s", redisInfo.Name, time.Now().Format("20060102150405"), suffix))
if err := handleRedisBackup(redisInfo, path.Dir(rollbackFile), path.Base(rollbackFile)); err != nil {
return fmt.Errorf("backup database %s for rollback before recover failed, err: %v", redisInfo.Name, err)
}
+32 -6
View File
@@ -11,6 +11,7 @@ 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/1Panel-dev/1Panel/backend/utils/cmd"
@@ -30,7 +31,7 @@ func (u *BackupService) WebsiteBackup(req dto.CommonBackup) error {
}
timeNow := time.Now().Format("20060102150405")
backupDir := fmt.Sprintf("%s/website/%s", localDir, req.Name)
backupDir := path.Join(localDir, fmt.Sprintf("website/%s", req.Name))
fileName := fmt.Sprintf("%s_%s.tar.gz", website.PrimaryDomain, timeNow)
if err := handleWebsiteBackup(&website, backupDir, fileName); err != nil {
return err
@@ -80,11 +81,11 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
temPathWithName := tmpPath + "/" + website.Alias
if !fileOp.Stat(tmpPath+"/website.json") || !fileOp.Stat(temPathWithName+".conf") || !fileOp.Stat(temPathWithName+".web.tar.gz") {
return errors.New("the wrong recovery package does not have .conf or .web.tar.gz files")
return buserr.WithDetail(constant.ErrBackupExist, ".conf or .web.tar.gz", nil)
}
if website.Type == constant.Deployment {
if !fileOp.Stat(temPathWithName + ".app.tar.gz") {
return errors.New("the wrong recovery package does not have .app.tar.gz files")
return buserr.WithDetail(constant.ErrBackupExist, ".app.tar.gz", nil)
}
}
var oldWebsite model.Website
@@ -95,13 +96,14 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
if err := json.Unmarshal(websiteJson, &oldWebsite); err != nil {
return fmt.Errorf("unmarshal app.json failed, err: %v", err)
}
if oldWebsite.Alias != website.Alias || oldWebsite.Type != website.Type || oldWebsite.ID != website.ID {
return errors.New("the current backup file does not match the application")
if err := checkValidOfWebsite(&oldWebsite, website); err != nil {
return err
}
isOk := false
if !isRollback {
rollbackFile := fmt.Sprintf("%s/original/website/%s_%s.tar.gz", global.CONF.System.BaseDir, website.Alias, time.Now().Format("20060102150405"))
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("website/%s_%s.tar.gz", website.Alias, time.Now().Format("20060102150405")))
if err := handleWebsiteBackup(website, path.Dir(rollbackFile), path.Base(rollbackFile)); err != nil {
return fmt.Errorf("backup website %s for rollback before recover failed, err: %v", website.Alias, err)
}
@@ -155,6 +157,7 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
return errors.New(string(stdout))
}
oldWebsite.ID = website.ID
if err := websiteRepo.SaveWithoutCtx(&oldWebsite); err != nil {
global.LOG.Errorf("handle save website data failed, err: %v", err)
return err
@@ -212,3 +215,26 @@ func handleWebsiteBackup(website *model.Website, backupDir, fileName string) err
return nil
}
func checkValidOfWebsite(oldWebsite, website *model.Website) error {
if oldWebsite.Alias != website.Alias || oldWebsite.Type != website.Type {
return buserr.WithDetail(constant.ErrBackupMatch, fmt.Sprintf("oldName: %s, oldType: %v", oldWebsite.Alias, oldWebsite.Type), nil)
}
if oldWebsite.AppInstallID != 0 {
_, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
if err != nil {
return buserr.WithDetail(constant.ErrBackupMatch, "app", nil)
}
}
if oldWebsite.RuntimeID != 0 {
if _, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID)); err != nil {
return buserr.WithDetail(constant.ErrBackupMatch, "runtime", nil)
}
}
if oldWebsite.WebsiteSSLID != 0 {
if _, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(website.WebsiteSSLID)); err != nil {
return buserr.WithDetail(constant.ErrBackupMatch, "ssl", nil)
}
}
return nil
}
+201 -26
View File
@@ -7,6 +7,7 @@ import (
"io"
"os"
"os/exec"
"path"
"path/filepath"
"sort"
"strconv"
@@ -18,6 +19,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/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/docker/docker/api/types"
@@ -62,6 +64,8 @@ type IContainerService interface {
TestCompose(req dto.ComposeCreate) (bool, error)
ComposeUpdate(req dto.ComposeUpdate) error
Prune(req dto.ContainerPrune) (dto.ContainerPruneReport, error)
LoadContainerLogs(req dto.OperationWithNameAndType) string
}
func NewIContainerService() IContainerService {
@@ -143,14 +147,7 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
IsFromApp = true
}
var ports []string
for _, port := range item.Ports {
itemPortStr := fmt.Sprintf("%v/%s", port.PrivatePort, port.Type)
if port.PublicPort != 0 {
itemPortStr = fmt.Sprintf("%s:%v->%v/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type)
}
ports = append(ports, itemPortStr)
}
ports := simplifyPort(item.Ports)
backDatas[i] = dto.ContainerInfo{
ContainerID: item.ID,
CreateTime: time.Unix(item.Created, 0).Format("2006-01-02 15:04:05"),
@@ -163,6 +160,14 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
IsFromApp: IsFromApp,
IsFromCompose: IsFromCompose,
}
if item.NetworkSettings != nil && len(item.NetworkSettings.Networks) > 0 {
networks := make([]string, 0, len(item.NetworkSettings.Networks))
for key := range item.NetworkSettings.Networks {
networks = append(networks, item.NetworkSettings.Networks[key].IPAddress)
}
sort.Strings(networks)
backDatas[i].Network = networks
}
}
return int64(total), backDatas, nil
@@ -181,7 +186,7 @@ func (u *ContainerService) List() ([]string, error) {
for _, container := range containers {
for _, name := range container.Names {
if len(name) != 0 {
datas = append(datas, strings.TrimLeft(name, "/"))
datas = append(datas, strings.TrimPrefix(name, "/"))
}
}
}
@@ -361,6 +366,7 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
}
}
data.Cmd = oldContainer.Config.Cmd
data.Entrypoint = oldContainer.Config.Entrypoint
data.Env = oldContainer.Config.Env
data.CPUShares = oldContainer.HostConfig.CPUShares
for key, val := range oldContainer.Config.Labels {
@@ -388,13 +394,7 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
if oldContainer.HostConfig.Memory != 0 {
data.Memory = float64(oldContainer.HostConfig.Memory) / 1024 / 1024
}
for _, bind := range oldContainer.HostConfig.Binds {
parts := strings.Split(bind, ":")
if len(parts) != 3 {
continue
}
data.Volumes = append(data.Volumes, dto.VolumeHelper{SourceDir: parts[0], ContainerDir: parts[1], Mode: parts[2]})
}
data.Volumes = loadVolumeBinds(oldContainer.HostConfig.Binds)
return &data, nil
}
@@ -431,13 +431,15 @@ func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error {
hostConf := oldContainer.HostConfig
var networkConf network.NetworkingConfig
if err := loadConfigInfo(req, config, hostConf, &networkConf); err != nil {
reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings)
return err
}
global.LOG.Infof("new container info %s has been update, now start to recreate", req.Name)
container, err := client.ContainerCreate(ctx, config, hostConf, &networkConf, &v1.Platform{}, req.Name)
if err != nil {
return fmt.Errorf("recreate contianer failed, err: %v", err)
reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings)
return fmt.Errorf("update contianer failed, err: %v", err)
}
global.LOG.Infof("update container %s successful! now check if the container is started.", req.Name)
if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil {
@@ -480,13 +482,14 @@ func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
}
global.LOG.Infof("new container info %s has been update, now start to recreate", req.Name)
container, err := client.ContainerCreate(ctx, config, hostConf, &network.NetworkingConfig{}, &v1.Platform{}, req.Name)
container, err := client.ContainerCreate(ctx, config, hostConf, &networkConf, &v1.Platform{}, req.Name)
if err != nil {
return fmt.Errorf("recreate contianer failed, err: %v", err)
reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings)
return fmt.Errorf("upgrade contianer failed, err: %v", err)
}
global.LOG.Infof("update container %s successful! now check if the container is started.", req.Name)
global.LOG.Infof("upgrade container %s successful! now check if the container is started.", req.Name)
if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil {
return fmt.Errorf("update successful but start failed, err: %v", err)
return fmt.Errorf("upgrade successful but start failed, err: %v", err)
}
return nil
@@ -530,11 +533,15 @@ func (u *ContainerService) ContainerLogClean(req dto.OperationWithName) error {
if err != nil {
return err
}
container, err := client.ContainerInspect(context.Background(), req.Name)
ctx := context.Background()
containerItem, err := client.ContainerInspect(ctx, req.Name)
if err != nil {
return err
}
file, err := os.OpenFile(container.LogPath, os.O_RDWR|os.O_CREATE, 0666)
if err := client.ContainerStop(ctx, containerItem.ID, container.StopOptions{}); err != nil {
return err
}
file, err := os.OpenFile(containerItem.LogPath, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
return err
}
@@ -544,17 +551,24 @@ func (u *ContainerService) ContainerLogClean(req dto.OperationWithName) error {
}
_, _ = file.Seek(0, 0)
files, _ := filepath.Glob(fmt.Sprintf("%s.*", container.LogPath))
files, _ := filepath.Glob(fmt.Sprintf("%s.*", containerItem.LogPath))
for _, file := range files {
_ = os.Remove(file)
}
if err := client.ContainerStart(ctx, containerItem.ID, types.ContainerStartOptions{}); err != nil {
return err
}
return nil
}
func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, container, since, tail string, follow bool) error {
if cmd.CheckIllegal(container, since, tail) {
return buserr.New(constant.ErrCmdIllegal)
}
command := fmt.Sprintf("docker logs %s", container)
if tail != "0" {
command += " -n " + tail
command += " --tail " + tail
}
if since != "all" {
command += " --since " + since
@@ -624,6 +638,48 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error
return &data, nil
}
func (u *ContainerService) LoadContainerLogs(req dto.OperationWithNameAndType) string {
filePath := ""
switch req.Type {
case "image-pull", "image-push", "image-build", "compose-create":
filePath = path.Join(global.CONF.System.TmpDir, fmt.Sprintf("docker_logs/%s", req.Name))
case "compose-detail":
client, err := docker.NewDockerClient()
if err != nil {
return ""
}
options := types.ContainerListOptions{All: true}
options.Filters = filters.NewArgs()
options.Filters.Add("label", fmt.Sprintf("%s=%s", composeProjectLabel, req.Name))
containers, err := client.ContainerList(context.Background(), options)
if err != nil {
return ""
}
for _, container := range containers {
config := container.Labels[composeConfigLabel]
workdir := container.Labels[composeWorkdirLabel]
if len(config) != 0 && len(workdir) != 0 && strings.Contains(config, workdir) {
filePath = config
break
} else {
filePath = workdir
break
}
}
if req.Type == "compose-create" {
filePath = path.Join(path.Dir(filePath), "compose.log")
}
}
if _, err := os.Stat(filePath); err != nil {
return ""
}
content, err := os.ReadFile(filePath)
if err != nil {
return ""
}
return string(content)
}
func stringsToMap(list []string) map[string]string {
var lableMap = make(map[string]string)
for _, label := range list {
@@ -787,11 +843,16 @@ func loadConfigInfo(req dto.ContainerOperate, config *container.Config, hostConf
}
config.Image = req.Image
config.Cmd = req.Cmd
config.Entrypoint = req.Entrypoint
config.Env = req.Env
config.Labels = stringsToMap(req.Labels)
config.ExposedPorts = exposeds
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{req.Network: {}}
if len(req.Network) != 0 {
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{req.Network: {}}
} else {
networkConf = &network.NetworkingConfig{}
}
hostConf.AutoRemove = req.AutoRemove
hostConf.CPUShares = req.CPUShares
@@ -811,3 +872,117 @@ func loadConfigInfo(req dto.ContainerOperate, config *container.Config, hostConf
}
return nil
}
func reCreateAfterUpdate(name string, client *client.Client, config *container.Config, hostConf *container.HostConfig, networkConf *types.NetworkSettings) {
ctx := context.Background()
var oldNetworkConf network.NetworkingConfig
if networkConf != nil {
for networkKey := range networkConf.Networks {
oldNetworkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}}
break
}
}
oldContainer, err := client.ContainerCreate(ctx, config, hostConf, &oldNetworkConf, &v1.Platform{}, name)
if err != nil {
global.LOG.Errorf("recreate after container update failed, err: %v", err)
return
}
if err := client.ContainerStart(ctx, oldContainer.ID, types.ContainerStartOptions{}); err != nil {
global.LOG.Errorf("restart after container update failed, err: %v", err)
}
}
func loadVolumeBinds(binds []string) []dto.VolumeHelper {
var datas []dto.VolumeHelper
for _, bind := range binds {
parts := strings.Split(bind, ":")
var volumeItem dto.VolumeHelper
if len(parts) > 3 {
continue
}
volumeItem.SourceDir = parts[0]
if len(parts) == 1 {
volumeItem.ContainerDir = parts[0]
volumeItem.Mode = "rw"
}
if len(parts) == 2 {
switch parts[1] {
case "r", "ro":
volumeItem.ContainerDir = parts[0]
volumeItem.Mode = "ro"
case "rw":
volumeItem.ContainerDir = parts[0]
volumeItem.Mode = "rw"
default:
volumeItem.ContainerDir = parts[1]
volumeItem.Mode = "rw"
}
}
if len(parts) == 3 {
volumeItem.ContainerDir = parts[1]
if parts[2] == "r" {
volumeItem.Mode = "ro"
} else {
volumeItem.Mode = parts[2]
}
}
datas = append(datas, volumeItem)
}
return datas
}
func simplifyPort(ports []types.Port) []string {
var datas []string
if len(ports) == 0 {
return datas
}
if len(ports) == 1 {
itemPortStr := fmt.Sprintf("%s:%v/%s", ports[0].IP, ports[0].PrivatePort, ports[0].Type)
if ports[0].PublicPort != 0 {
itemPortStr = fmt.Sprintf("%s:%v->%v/%s", ports[0].IP, ports[0].PublicPort, ports[0].PrivatePort, ports[0].Type)
}
datas = append(datas, itemPortStr)
return datas
}
sort.Slice(ports, func(i, j int) bool {
return ports[i].PrivatePort < ports[j].PrivatePort
})
start := ports[0]
for i := 1; i < len(ports); i++ {
if ports[i].PrivatePort != ports[i-1].PrivatePort+1 || ports[i].IP != ports[i-1].IP || ports[i].PublicPort != ports[i-1].PublicPort+1 || ports[i].Type != ports[i-1].Type {
if ports[i-1].PrivatePort == start.PrivatePort {
itemPortStr := fmt.Sprintf("%s:%v/%s", start.IP, start.PrivatePort, start.Type)
if start.PublicPort != 0 {
itemPortStr = fmt.Sprintf("%s:%v->%v/%s", start.IP, start.PublicPort, start.PrivatePort, start.Type)
}
datas = append(datas, itemPortStr)
} else {
itemPortStr := fmt.Sprintf("%s:%v-%v/%s", start.IP, start.PrivatePort, ports[i-1].PrivatePort, start.Type)
if start.PublicPort != 0 {
itemPortStr = fmt.Sprintf("%s:%v-%v->%v-%v/%s", start.IP, start.PublicPort, ports[i-1].PublicPort, start.PrivatePort, ports[i-1].PrivatePort, start.Type)
}
datas = append(datas, itemPortStr)
}
start = ports[i]
} else if i == len(ports)-1 {
if ports[i].PrivatePort == start.PrivatePort {
itemPortStr := fmt.Sprintf("%s:%v/%s", start.IP, start.PrivatePort, start.Type)
if start.PublicPort != 0 {
itemPortStr = fmt.Sprintf("%s:%v->%v/%s", start.IP, start.PublicPort, start.PrivatePort, start.Type)
}
datas = append(datas, itemPortStr)
} else {
itemPortStr := fmt.Sprintf("%s:%v-%v/%s", start.IP, start.PrivatePort, ports[i].PrivatePort, start.Type)
if start.PublicPort != 0 {
itemPortStr = fmt.Sprintf("%s:%v-%v->%v-%v/%s", start.IP, start.PublicPort, ports[i].PublicPort, start.PrivatePort, ports[i].PrivatePort, start.Type)
}
datas = append(datas, itemPortStr)
}
}
}
return datas
}
+25 -4
View File
@@ -14,8 +14,10 @@ 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/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/compose"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/docker/docker/api/types"
@@ -127,6 +129,9 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
}
func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
if cmd.CheckIllegal(req.Path) {
return false, buserr.New(constant.ErrCmdIllegal)
}
composeItem, _ := composeRepo.GetRecord(commonRepo.WithByName(req.Name))
if composeItem.ID != 0 {
return false, constant.ErrRecordExist
@@ -143,16 +148,26 @@ func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
}
func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error) {
if cmd.CheckIllegal(req.Name, req.Path) {
return "", buserr.New(constant.ErrCmdIllegal)
}
if err := u.loadPath(&req); err != nil {
return "", err
}
global.LOG.Infof("docker-compose.yml %s create successful, start to docker-compose up", req.Name)
if req.From == "path" {
req.Name = path.Base(strings.ReplaceAll(req.Path, "/"+path.Base(req.Path), ""))
req.Name = path.Base(path.Dir(req.Path))
}
logName := path.Dir(req.Path) + "/compose.log"
file, err := os.OpenFile(logName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
dockerLogDir := path.Join(global.CONF.System.TmpDir, "docker_logs")
if _, err := os.Stat(dockerLogDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(dockerLogDir, os.ModePerm); err != nil {
return "", err
}
}
logItem := fmt.Sprintf("%s/compose_create_%s_%s.log", dockerLogDir, req.Name, time.Now().Format("20060102150405"))
file, err := os.OpenFile(logItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return "", err
}
@@ -173,10 +188,13 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
_, _ = file.WriteString("docker-compose up successful!")
}()
return logName, nil
return path.Base(logItem), nil
}
func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
if cmd.CheckIllegal(req.Path, req.Operation) {
return buserr.New(constant.ErrCmdIllegal)
}
if _, err := os.Stat(req.Path); err != nil {
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
}
@@ -195,6 +213,9 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
}
func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error {
if cmd.CheckIllegal(req.Name, req.Path) {
return buserr.New(constant.ErrCmdIllegal)
}
if _, err := os.Stat(req.Path); err != nil {
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
}
+32 -15
View File
@@ -9,6 +9,7 @@ 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"
@@ -29,6 +30,8 @@ type ICronjobService interface {
Download(down dto.CronjobDownload) (string, error)
StartJob(cronjob *model.Cronjob) (int, error)
CleanRecord(req dto.CronjobClean) error
LoadRecordLog(req dto.OperateByID) (string, error)
}
func NewICronjobService() ICronjobService {
@@ -43,7 +46,7 @@ func (u *CronjobService) SearchWithPage(search dto.SearchWithPage) (int64, inter
if err := copier.Copy(&item, &cronjob); err != nil {
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
if item.Type == "website" || item.Type == "database" || item.Type == "directory" {
if item.Type == "app" || item.Type == "website" || item.Type == "database" || item.Type == "directory" || item.Type == "snapshot" {
backup, _ := backupRepo.Get(commonRepo.WithByID(uint(item.TargetDirID)))
if len(backup.Type) != 0 {
item.TargetDir = backup.Type
@@ -80,12 +83,27 @@ func (u *CronjobService) SearchRecords(search dto.SearchRecord) (int64, interfac
return total, dtoCronjobs, err
}
func (u *CronjobService) LoadRecordLog(req dto.OperateByID) (string, error) {
record, err := cronjobRepo.GetRecord(commonRepo.WithByID(req.ID))
if err != nil {
return "", err
}
if _, err := os.Stat(record.Records); err != nil {
return "", buserr.New("ErrHttpReqNotFound")
}
content, err := os.ReadFile(record.Records)
if err != nil {
return "", err
}
return string(content), nil
}
func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
cronjob, err := cronjobRepo.Get(commonRepo.WithByID(req.CronjobID))
if err != nil {
return err
}
if req.CleanData && (cronjob.Type == "database" || cronjob.Type == "website" || cronjob.Type == "directory") {
if req.CleanData && (cronjob.Type == "app" || cronjob.Type == "database" || cronjob.Type == "website" || cronjob.Type == "directory" || cronjob.Type == "snapshot") {
cronjob.RetainCopies = 0
backup, err := backupRepo.Get(commonRepo.WithByID(uint(cronjob.TargetDirID)))
if err != nil {
@@ -123,12 +141,8 @@ func (u *CronjobService) Download(down dto.CronjobDownload) (string, error) {
if record.ID == 0 {
return "", constant.ErrRecordNotFound
}
cronjob, _ := cronjobRepo.Get(commonRepo.WithByID(record.CronjobID))
if cronjob.ID == 0 {
return "", constant.ErrRecordNotFound
}
backup, _ := backupRepo.Get(commonRepo.WithByID(down.BackupAccountID))
if cronjob.ID == 0 {
if backup.ID == 0 {
return "", constant.ErrRecordNotFound
}
if backup.Type == "LOCAL" || record.FromLocal {
@@ -137,15 +151,17 @@ func (u *CronjobService) Download(down dto.CronjobDownload) (string, error) {
}
return record.File, nil
}
client, err := NewIBackupService().NewClient(&backup)
if err != nil {
return "", err
}
tempPath := fmt.Sprintf("%s/download/%s", constant.DataDir, record.File)
_ = os.MkdirAll(path.Dir(tempPath), os.ModePerm)
isOK, err := client.Download(record.File, tempPath)
if !isOK || err != nil {
return "", err
if _, err := os.Stat(tempPath); err != nil && os.IsNotExist(err) {
client, err := NewIBackupService().NewClient(&backup)
if err != nil {
return "", err
}
_ = os.MkdirAll(path.Dir(tempPath), os.ModePerm)
isOK, err := client.Download(record.File, tempPath)
if !isOK || err != nil {
return "", err
}
}
return tempPath, nil
}
@@ -245,6 +261,7 @@ func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
upMap["hour"] = req.Hour
upMap["minute"] = req.Minute
upMap["second"] = req.Second
upMap["app_id"] = req.AppID
upMap["website"] = req.Website
upMap["exclusion_rules"] = req.ExclusionRules
upMap["db_name"] = req.DBName
+144 -29
View File
@@ -5,12 +5,13 @@ import (
"fmt"
"os"
"path"
"strconv"
"strings"
"sync"
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage"
@@ -38,6 +39,10 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
message, err = u.handleShell(cronjob.Type, cronjob.Name, cronjob.Script)
}
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
case "snapshot":
messageItem := ""
messageItem, record.File, err = u.handleSnapshot(cronjob, record.StartTime)
message = []byte(messageItem)
case "curl":
if len(cronjob.URL) == 0 {
return
@@ -47,9 +52,7 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
case "ntp":
err = u.handleNtpSync()
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
case "website":
record.File, err = u.handleBackup(cronjob, record.StartTime)
case "database":
case "website", "database", "app":
record.File, err = u.handleBackup(cronjob, record.StartTime)
case "directory":
if len(cronjob.SourceDir) == 0 {
@@ -62,6 +65,7 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
global.LOG.Errorf("cut website log file failed, err: %v", err)
}
}
if err != nil {
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), string(message))
return
@@ -85,7 +89,7 @@ func (u *CronjobService) handleShell(cronType, cornName, script string) ([]byte,
}
stdout, err := cmd.ExecCronjobWithTimeOut(script, handleDir, 24*time.Hour)
if err != nil {
return nil, err
return []byte(stdout), err
}
return []byte(stdout), nil
}
@@ -118,18 +122,17 @@ func (u *CronjobService) handleBackup(cronjob *model.Cronjob, startTime time.Tim
switch cronjob.Type {
case "database":
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
if err != nil {
return "", err
}
paths, err := u.handleDatabase(*cronjob, app, backup, startTime)
paths, err := u.handleDatabase(*cronjob, backup, startTime)
return strings.Join(paths, ","), err
case "app":
paths, err := u.handleApp(*cronjob, backup, startTime)
return strings.Join(paths, ","), err
case "website":
paths, err := u.handleWebsite(*cronjob, backup, startTime)
return strings.Join(paths, ","), err
default:
fileName := fmt.Sprintf("directory%s_%s.tar.gz", strings.ReplaceAll(cronjob.SourceDir, "/", "_"), startTime.Format("20060102150405"))
backupDir := fmt.Sprintf("%s/%s/%s", localDir, cronjob.Type, cronjob.Name)
backupDir := path.Join(localDir, fmt.Sprintf("%s/%s", cronjob.Type, cronjob.Name))
itemFileDir := fmt.Sprintf("%s/%s", cronjob.Type, cronjob.Name)
global.LOG.Infof("handle tar %s to %s", backupDir, fileName)
if err := handleTar(cronjob.SourceDir, backupDir, fileName, cronjob.ExclusionRules); err != nil {
@@ -190,6 +193,10 @@ func (u *CronjobService) HandleRmExpired(backType, backupPath, localDir string,
fileItem = strings.TrimPrefix(file, localDir+"/")
}
}
if cronjob.Type == "snapshot" {
_ = snapshotRepo.Delete(commonRepo.WithByName(strings.TrimSuffix(path.Base(fileItem), ".tar.gz")))
}
_, _ = backClient.Delete(fileItem)
}
}
@@ -225,12 +232,14 @@ func handleTar(sourceDir, targetDir, name, exclusionRules string) error {
path = sourceDir
}
commands := fmt.Sprintf("tar zcvf %s %s %s", targetDir+"/"+name, excludeRules, path)
commands := fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read -zcf %s %s %s", targetDir+"/"+name, excludeRules, path)
global.LOG.Debug(commands)
stdout, err := cmd.ExecWithTimeOut(commands, 24*time.Hour)
if err != nil {
global.LOG.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err)
return errors.New(stdout)
if len(stdout) != 0 {
global.LOG.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err)
return fmt.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err)
}
}
return nil
}
@@ -252,22 +261,25 @@ func handleUnTar(sourceFile, targetDir string) error {
return nil
}
func (u *CronjobService) handleDatabase(cronjob model.Cronjob, app *repo.RootInfo, backup model.BackupAccount, startTime time.Time) ([]string, error) {
func (u *CronjobService) handleDatabase(cronjob model.Cronjob, backup model.BackupAccount, startTime time.Time) ([]string, error) {
var paths []string
localDir, err := loadLocalDir()
if err != nil {
return paths, err
}
var dblist []string
var dbs []model.DatabaseMysql
if cronjob.DBName == "all" {
mysqlService := NewIMysqlService()
dblist, err = mysqlService.ListDBName()
dbs, err = mysqlRepo.List()
if err != nil {
return paths, err
}
} else {
dblist = append(dblist, cronjob.DBName)
itemID, _ := (strconv.Atoi(cronjob.DBName))
dbs, err = mysqlRepo.List(commonRepo.WithByID(uint(itemID)))
if err != nil {
return paths, err
}
}
var client cloud_storage.CloudStorageClient
@@ -278,20 +290,22 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, app *repo.RootInf
}
}
for _, dbName := range dblist {
for _, dbInfo := range dbs {
var record model.BackupRecord
record.Type = "mysql"
record.Name = app.Name
database, _ := databaseRepo.Get(commonRepo.WithByName(dbInfo.MysqlName))
record.Type = database.Type
record.Source = "LOCAL"
record.BackupType = backup.Type
backupDir := fmt.Sprintf("%s/database/mysql/%s/%s", localDir, app.Name, dbName)
record.FileName = fmt.Sprintf("db_%s_%s.sql.gz", dbName, startTime.Format("20060102150405"))
if err = handleMysqlBackup(app, backupDir, dbName, record.FileName); err != nil {
record.Name = dbInfo.MysqlName
backupDir := path.Join(localDir, fmt.Sprintf("database/%s/%s/%s", database.Type, record.Name, dbInfo.Name))
record.FileName = fmt.Sprintf("db_%s_%s.sql.gz", dbInfo.Name, startTime.Format("20060102150405"))
if err = handleMysqlBackup(dbInfo.MysqlName, dbInfo.Name, backupDir, record.FileName); err != nil {
return paths, err
}
record.DetailName = dbName
record.DetailName = dbInfo.Name
record.FileDir = backupDir
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
@@ -359,10 +373,10 @@ func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime t
wg.Done()
return
}
websiteLogDir := path.Join(baseDir, website.PrimaryDomain, "log")
websiteLogDir := path.Join(baseDir, website.Alias, "log")
srcAccessLogPath := path.Join(websiteLogDir, "access.log")
srcErrorLogPath := path.Join(websiteLogDir, "error.log")
dstLogDir := path.Join(global.CONF.System.Backup, "log", "website", website.PrimaryDomain)
dstLogDir := path.Join(global.CONF.System.Backup, "log", "website", website.Alias)
if !fileOp.Stat(dstLogDir) {
_ = os.MkdirAll(dstLogDir, 0755)
}
@@ -394,6 +408,83 @@ func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime t
return strings.Join(filePaths, ","), nil
}
func (u *CronjobService) handleApp(cronjob model.Cronjob, backup model.BackupAccount, startTime time.Time) ([]string, error) {
var paths []string
localDir, err := loadLocalDir()
if err != nil {
return paths, err
}
var applist []model.AppInstall
if cronjob.AppID == "all" {
applist, err = appInstallRepo.ListBy()
if err != nil {
return paths, err
}
} else {
itemID, _ := (strconv.Atoi(cronjob.AppID))
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(uint(itemID)))
if err != nil {
return paths, err
}
applist = append(applist, app)
}
var client cloud_storage.CloudStorageClient
if backup.Type != "LOCAL" {
client, err = NewIBackupService().NewClient(&backup)
if err != nil {
return paths, err
}
}
for _, app := range applist {
var record model.BackupRecord
record.Type = "app"
record.Name = app.App.Key
record.DetailName = app.Name
record.Source = "LOCAL"
record.BackupType = backup.Type
backupDir := path.Join(localDir, fmt.Sprintf("app/%s/%s", app.App.Key, app.Name))
record.FileDir = backupDir
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
record.Source = backup.Type
record.FileDir = strings.TrimPrefix(backupDir, localDir+"/")
}
record.FileName = fmt.Sprintf("app_%s_%s.tar.gz", app.Name, startTime.Format("20060102150405"))
if err := handleAppBackup(&app, backupDir, record.FileName); err != nil {
return paths, err
}
if err := backupRepo.CreateRecord(&record); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err)
return paths, err
}
if backup.Type != "LOCAL" {
if !cronjob.KeepLocal {
defer func() {
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName))
}()
}
if len(backup.BackupPath) != 0 {
itemPath := strings.TrimPrefix(backup.BackupPath, "/")
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
itemFileDir = itemPath + itemFileDir
}
if _, err = client.Upload(backupDir+"/"+record.FileName, itemFileDir+"/"+record.FileName); err != nil {
return paths, err
}
}
if backup.Type == "LOCAL" || cronjob.KeepLocal {
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
} else {
paths = append(paths, fmt.Sprintf("%s/%s", itemFileDir, record.FileName))
}
}
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
return paths, nil
}
func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.BackupAccount, startTime time.Time) ([]string, error) {
var paths []string
localDir, err := loadLocalDir()
@@ -429,7 +520,7 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.Backu
if err != nil {
return paths, err
}
backupDir := fmt.Sprintf("%s/website/%s", localDir, website.PrimaryDomain)
backupDir := path.Join(localDir, fmt.Sprintf("website/%s", website.PrimaryDomain))
record.FileDir = backupDir
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
@@ -469,3 +560,27 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.Backu
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
return paths, nil
}
func (u *CronjobService) handleSnapshot(cronjob *model.Cronjob, startTime time.Time) (string, string, error) {
backup, err := backupRepo.Get(commonRepo.WithByID(uint(cronjob.TargetDirID)))
if err != nil {
return "", "", err
}
client, err := NewIBackupService().NewClient(&backup)
if err != nil {
return "", "", err
}
req := dto.SnapshotCreate{
From: backup.Type,
}
message, name, err := NewISnapshotService().HandleSnapshot(true, req, startTime.Format("20060102150405"))
if err != nil {
return message, "", err
}
path := path.Join(strings.TrimPrefix(backup.BackupPath, "/"), "system_snapshot", name+".tar.gz")
u.HandleRmExpired(backup.Type, backup.BackupPath, "", cronjob, client)
return message, path, nil
}
+159
View File
@@ -0,0 +1,159 @@
package service
import (
"context"
"fmt"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
"github.com/1Panel-dev/1Panel/backend/utils/mysql"
"github.com/1Panel-dev/1Panel/backend/utils/mysql/client"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
)
type DatabaseService struct{}
type IDatabaseService interface {
Get(name string) (dto.DatabaseInfo, error)
SearchWithPage(search dto.DatabaseSearch) (int64, interface{}, error)
CheckDatabase(req dto.DatabaseCreate) bool
Create(req dto.DatabaseCreate) error
Update(req dto.DatabaseUpdate) error
Delete(id uint) error
List(dbType string) ([]dto.DatabaseOption, error)
}
func NewIDatabaseService() IDatabaseService {
return &DatabaseService{}
}
func (u *DatabaseService) SearchWithPage(search dto.DatabaseSearch) (int64, interface{}, error) {
total, dbs, err := databaseRepo.Page(search.Page, search.PageSize,
databaseRepo.WithTypeList(search.Type),
commonRepo.WithLikeName(search.Info),
databaseRepo.WithoutByFrom("local"),
)
var datas []dto.DatabaseInfo
for _, db := range dbs {
var item dto.DatabaseInfo
if err := copier.Copy(&item, &db); err != nil {
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
datas = append(datas, item)
}
return total, datas, err
}
func (u *DatabaseService) Get(name string) (dto.DatabaseInfo, error) {
var data dto.DatabaseInfo
remote, err := databaseRepo.Get(commonRepo.WithByName(name))
if err != nil {
return data, err
}
if err := copier.Copy(&data, &remote); err != nil {
return data, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
return data, nil
}
func (u *DatabaseService) List(dbType string) ([]dto.DatabaseOption, error) {
dbs, err := databaseRepo.GetList(databaseRepo.WithTypeList(dbType))
var datas []dto.DatabaseOption
for _, db := range dbs {
var item dto.DatabaseOption
if err := copier.Copy(&item, &db); err != nil {
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
item.Database = db.Name
datas = append(datas, item)
}
return datas, err
}
func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
if _, err := mysql.NewMysqlClient(client.DBInfo{
From: "remote",
Address: req.Address,
Port: req.Port,
Username: req.Username,
Password: req.Password,
Timeout: 6,
}); err != nil {
return false
}
return true
}
func (u *DatabaseService) Create(req dto.DatabaseCreate) error {
db, _ := databaseRepo.Get(commonRepo.WithByName(req.Name))
if db.ID != 0 {
if db.From == "local" {
return buserr.New(constant.ErrLocalExist)
}
return constant.ErrRecordExist
}
if _, err := mysql.NewMysqlClient(client.DBInfo{
From: "remote",
Address: req.Address,
Port: req.Port,
Username: req.Username,
Password: req.Password,
Timeout: 6,
}); err != nil {
return err
}
if err := copier.Copy(&db, &req); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
}
if err := databaseRepo.Create(context.Background(), &db); err != nil {
return err
}
return nil
}
func (u *DatabaseService) Delete(id uint) error {
db, _ := databaseRepo.Get(commonRepo.WithByID(id))
if db.ID == 0 {
return constant.ErrRecordNotFound
}
if err := databaseRepo.Delete(context.Background(), commonRepo.WithByID(id)); err != nil {
return err
}
if db.From != "local" {
if err := mysqlRepo.Delete(context.Background(), mysqlRepo.WithByMysqlName(db.Name)); err != nil {
return err
}
}
return nil
}
func (u *DatabaseService) Update(req dto.DatabaseUpdate) error {
if _, err := mysql.NewMysqlClient(client.DBInfo{
From: "remote",
Address: req.Address,
Port: req.Port,
Username: req.Username,
Password: req.Password,
Timeout: 300,
}); err != nil {
return err
}
pass, err := encrypt.StringEncrypt(req.Password)
if err != nil {
return fmt.Errorf("decrypt database password failed, err: %v", err)
}
upMap := make(map[string]interface{})
upMap["type"] = req.Type
upMap["version"] = req.Version
upMap["address"] = req.Address
upMap["port"] = req.Port
upMap["username"] = req.Username
upMap["password"] = pass
upMap["description"] = req.Description
return databaseRepo.Update(req.ID, upMap)
}
+296 -258
View File
@@ -7,6 +7,7 @@ import (
"fmt"
"os"
"os/exec"
"path"
"regexp"
"strconv"
"strings"
@@ -17,8 +18,12 @@ 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/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/compose"
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
"github.com/1Panel-dev/1Panel/backend/utils/mysql"
"github.com/1Panel-dev/1Panel/backend/utils/mysql/client"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
@@ -27,28 +32,36 @@ import (
type MysqlService struct{}
type IMysqlService interface {
SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error)
ListDBName() ([]string, error)
SearchWithPage(search dto.MysqlDBSearch) (int64, interface{}, error)
ListDBOption() ([]dto.MysqlOption, error)
Create(ctx context.Context, req dto.MysqlDBCreate) (*model.DatabaseMysql, error)
LoadFromRemote(req dto.MysqlLoadDB) error
ChangeAccess(info dto.ChangeDBInfo) error
ChangePassword(info dto.ChangeDBInfo) error
UpdateVariables(updates []dto.MysqlVariablesUpdate) error
UpdateVariables(req dto.MysqlVariablesUpdate) error
UpdateConfByFile(info dto.MysqlConfUpdateByFile) error
UpdateDescription(req dto.UpdateDescription) error
DeleteCheck(id uint) ([]string, error)
DeleteCheck(req dto.MysqlDBDeleteCheck) ([]string, error)
Delete(ctx context.Context, req dto.MysqlDBDelete) error
LoadStatus() (*dto.MysqlStatus, error)
LoadVariables() (*dto.MysqlVariables, error)
LoadBaseInfo() (*dto.DBBaseInfo, error)
LoadRemoteAccess() (bool, error)
LoadStatus(req dto.OperationWithNameAndType) (*dto.MysqlStatus, error)
LoadVariables(req dto.OperationWithNameAndType) (*dto.MysqlVariables, error)
LoadBaseInfo(req dto.OperationWithNameAndType) (*dto.DBBaseInfo, error)
LoadRemoteAccess(req dto.OperationWithNameAndType) (bool, error)
LoadDatabaseFile(req dto.OperationWithNameAndType) (string, error)
}
func NewIMysqlService() IMysqlService {
return &MysqlService{}
}
func (u *MysqlService) SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error) {
total, mysqls, err := mysqlRepo.Page(search.Page, search.PageSize, commonRepo.WithLikeName(search.Info), commonRepo.WithOrderRuleBy(search.OrderBy, search.Order))
func (u *MysqlService) SearchWithPage(search dto.MysqlDBSearch) (int64, interface{}, error) {
total, mysqls, err := mysqlRepo.Page(search.Page, search.PageSize,
mysqlRepo.WithByMysqlName(search.Database),
commonRepo.WithLikeName(search.Info),
commonRepo.WithOrderRuleBy(search.OrderBy, search.Order),
)
var dtoMysqls []dto.MysqlDBInfo
for _, mysql := range mysqls {
var item dto.MysqlDBInfo
@@ -60,151 +73,232 @@ func (u *MysqlService) SearchWithPage(search dto.SearchWithPage) (int64, interfa
return total, dtoMysqls, err
}
func (u *MysqlService) ListDBName() ([]string, error) {
func (u *MysqlService) ListDBOption() ([]dto.MysqlOption, error) {
mysqls, err := mysqlRepo.List()
var dbNames []string
for _, mysql := range mysqls {
dbNames = append(dbNames, mysql.Name)
}
return dbNames, err
}
var formatMap = map[string]string{
"utf8": "utf8_general_ci",
"utf8mb4": "utf8mb4_general_ci",
"gbk": "gbk_chinese_ci",
"big5": "big5_chinese_ci",
}
func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*model.DatabaseMysql, error) {
if req.Username == "root" {
return nil, errors.New("Cannot set root as user name")
}
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
if err != nil {
return nil, err
}
mysql, _ := mysqlRepo.Get(commonRepo.WithByName(req.Name))
databases, err := databaseRepo.GetList(databaseRepo.WithTypeList("mysql,mariadb"))
if err != nil {
return nil, err
}
var dbs []dto.MysqlOption
for _, mysql := range mysqls {
var item dto.MysqlOption
if err := copier.Copy(&item, &mysql); err != nil {
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
item.Database = mysql.MysqlName
for _, database := range databases {
if database.Name == item.Database {
item.Type = database.Type
}
}
dbs = append(dbs, item)
}
return dbs, err
}
func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*model.DatabaseMysql, error) {
if cmd.CheckIllegal(req.Name, req.Username, req.Password, req.Format, req.Permission) {
return nil, buserr.New(constant.ErrCmdIllegal)
}
mysql, _ := mysqlRepo.Get(commonRepo.WithByName(req.Name), mysqlRepo.WithByMysqlName(req.Database), databaseRepo.WithByFrom(req.From))
if mysql.ID != 0 {
return nil, constant.ErrRecordExist
}
if err := copier.Copy(&mysql, &req); err != nil {
var createItem model.DatabaseMysql
if err := copier.Copy(&createItem, &req); err != nil {
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
createSql := fmt.Sprintf("create database `%s` default character set %s collate %s", req.Name, req.Format, formatMap[req.Format])
if err := excSQL(app.ContainerName, app.Password, createSql); err != nil {
if strings.Contains(err.Error(), "ERROR 1007") {
return nil, buserr.New(constant.ErrDatabaseIsExist)
}
if req.From == "local" && req.Username == "root" {
return nil, errors.New("Cannot set root as user name")
}
cli, version, err := LoadMysqlClientByFrom(req.Database)
if err != nil {
return nil, err
}
if err := u.createUser(app.ContainerName, app.Password, app.Version, req); err != nil {
createItem.MysqlName = req.Database
defer cli.Close()
if err := cli.Create(client.CreateInfo{
Name: req.Name,
Format: req.Format,
Username: req.Username,
Password: req.Password,
Permission: req.Permission,
Version: version,
Timeout: 300,
}); err != nil {
return nil, err
}
global.LOG.Infof("create database %s successful!", req.Name)
mysql.MysqlName = app.Name
if err := mysqlRepo.Create(ctx, &mysql); err != nil {
if err := mysqlRepo.Create(ctx, &createItem); err != nil {
return nil, err
}
return &mysql, nil
return &createItem, nil
}
func (u *MysqlService) LoadFromRemote(req dto.MysqlLoadDB) error {
client, version, err := LoadMysqlClientByFrom(req.Database)
if err != nil {
return err
}
databases, err := mysqlRepo.List(mysqlRepo.WithByMysqlName(req.Database))
if err != nil {
return err
}
datas, err := client.SyncDB(version)
if err != nil {
return err
}
for _, data := range datas {
hasOld := false
for _, oldData := range databases {
if strings.EqualFold(oldData.Name, data.Name) && strings.EqualFold(oldData.MysqlName, data.MysqlName) {
hasOld = true
break
}
}
if !hasOld {
var createItem model.DatabaseMysql
if err := copier.Copy(&createItem, &data); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
}
if err := mysqlRepo.Create(context.Background(), &createItem); err != nil {
return err
}
}
}
return nil
}
func (u *MysqlService) UpdateDescription(req dto.UpdateDescription) error {
return mysqlRepo.Update(req.ID, map[string]interface{}{"description": req.Description})
}
func (u *MysqlService) DeleteCheck(id uint) ([]string, error) {
func (u *MysqlService) DeleteCheck(req dto.MysqlDBDeleteCheck) ([]string, error) {
var appInUsed []string
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
db, err := mysqlRepo.Get(commonRepo.WithByID(req.ID))
if err != nil {
return appInUsed, err
}
db, err := mysqlRepo.Get(commonRepo.WithByID(id))
if err != nil {
return appInUsed, err
}
apps, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(db.ID))
for _, app := range apps {
appInstall, _ := appInstallRepo.GetFirst(commonRepo.WithByID(app.AppInstallId))
if appInstall.ID != 0 {
appInUsed = append(appInUsed, appInstall.Name)
if db.From == "local" {
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database)
if err != nil {
return appInUsed, err
}
apps, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(db.ID))
for _, app := range apps {
appInstall, _ := appInstallRepo.GetFirst(commonRepo.WithByID(app.AppInstallId))
if appInstall.ID != 0 {
appInUsed = append(appInUsed, appInstall.Name)
}
}
} else {
apps, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithResourceId(db.ID))
for _, app := range apps {
appInstall, _ := appInstallRepo.GetFirst(commonRepo.WithByID(app.AppInstallId))
if appInstall.ID != 0 {
appInUsed = append(appInUsed, appInstall.Name)
}
}
}
return appInUsed, nil
}
func (u *MysqlService) Delete(ctx context.Context, req dto.MysqlDBDelete) error {
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
if err != nil && !req.ForceDelete {
return err
}
db, err := mysqlRepo.Get(commonRepo.WithByID(req.ID))
if err != nil && !req.ForceDelete {
return err
}
if strings.HasPrefix(app.Version, "5.6") {
if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop user '%s'@'%s'", db.Username, db.Permission)); err != nil && !req.ForceDelete {
return err
}
} else {
if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop user if exists '%s'@'%s'", db.Username, db.Permission)); err != nil && !req.ForceDelete {
return err
}
}
if err := excSQL(app.ContainerName, app.Password, fmt.Sprintf("drop database if exists `%s`", db.Name)); err != nil && !req.ForceDelete {
cli, version, err := LoadMysqlClientByFrom(req.Database)
if err != nil {
return err
}
global.LOG.Info("execute delete database sql successful, now start to drop uploads and records")
uploadDir := fmt.Sprintf("%s/1panel/uploads/database/mysql/%s/%s", global.CONF.System.BaseDir, app.Name, db.Name)
if _, err := os.Stat(uploadDir); err == nil {
_ = os.RemoveAll(uploadDir)
defer cli.Close()
if err := cli.Delete(client.DeleteInfo{
Name: db.Name,
Version: version,
Username: db.Username,
Permission: db.Permission,
Timeout: 300,
}); err != nil && !req.ForceDelete {
return err
}
if req.DeleteBackup {
uploadDir := path.Join(global.CONF.System.BaseDir, fmt.Sprintf("1panel/uploads/database/%s/%s/%s", req.Type, req.Database, db.Name))
if _, err := os.Stat(uploadDir); err == nil {
_ = os.RemoveAll(uploadDir)
}
localDir, err := loadLocalDir()
if err != nil && !req.ForceDelete {
return err
}
backupDir := fmt.Sprintf("%s/database/mysql/%s/%s", localDir, db.MysqlName, db.Name)
backupDir := path.Join(localDir, fmt.Sprintf("database/%s/%s/%s", req.Type, db.MysqlName, db.Name))
if _, err := os.Stat(backupDir); err == nil {
_ = os.RemoveAll(backupDir)
}
global.LOG.Infof("delete database %s-%s backups successful", app.Name, db.Name)
_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType(req.Type), commonRepo.WithByName(req.Database), backupRepo.WithByDetailName(db.Name))
global.LOG.Infof("delete database %s-%s backups successful", req.Database, db.Name)
}
_ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType("mysql"), commonRepo.WithByName(app.Name), backupRepo.WithByDetailName(db.Name))
_ = mysqlRepo.Delete(ctx, commonRepo.WithByID(db.ID))
return nil
}
func (u *MysqlService) ChangePassword(info dto.ChangeDBInfo) error {
var (
mysql model.DatabaseMysql
err error
)
if info.ID != 0 {
mysql, err = mysqlRepo.Get(commonRepo.WithByID(info.ID))
if err != nil {
return err
}
func (u *MysqlService) ChangePassword(req dto.ChangeDBInfo) error {
if cmd.CheckIllegal(req.Value) {
return buserr.New(constant.ErrCmdIllegal)
}
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
cli, version, err := LoadMysqlClientByFrom(req.Database)
if err != nil {
return err
}
defer cli.Close()
var (
mysqlData model.DatabaseMysql
passwordInfo client.PasswordChangeInfo
)
passwordInfo.Password = req.Value
passwordInfo.Timeout = 300
passwordInfo.Version = version
passwordChangeCMD := fmt.Sprintf("set password for '%s'@'%s' = password('%s')", mysql.Username, mysql.Permission, info.Value)
if !strings.HasPrefix(app.Version, "5.7") && !strings.HasPrefix(app.Version, "5.6") {
passwordChangeCMD = fmt.Sprintf("ALTER USER '%s'@'%s' IDENTIFIED WITH mysql_native_password BY '%s';", mysql.Username, mysql.Permission, info.Value)
if req.ID != 0 {
mysqlData, err = mysqlRepo.Get(commonRepo.WithByID(req.ID))
if err != nil {
return err
}
passwordInfo.Name = mysqlData.Name
passwordInfo.Username = mysqlData.Username
passwordInfo.Permission = mysqlData.Permission
} else {
passwordInfo.Username = "root"
}
if info.ID != 0 {
appRess, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(mysql.ID))
if err := cli.ChangePassword(passwordInfo); err != nil {
return err
}
if req.ID != 0 {
var appRess []model.AppInstallResource
if req.From == "local" {
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database)
if err != nil {
return err
}
appRess, _ = appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(mysqlData.ID))
} else {
appRess, _ = appInstallResourceRepo.GetBy(appInstallResourceRepo.WithResourceId(mysqlData.ID))
}
for _, appRes := range appRess {
appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(appRes.AppInstallId))
if err != nil {
@@ -216,140 +310,93 @@ func (u *MysqlService) ChangePassword(info 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, info.Value); err != nil {
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", true, req.Value); err != nil {
return err
}
}
if err := excuteSql(app.ContainerName, app.Password, passwordChangeCMD); err != nil {
return err
global.LOG.Info("execute password change sql successful")
pass, err := encrypt.StringEncrypt(req.Value)
if err != nil {
return fmt.Errorf("decrypt database db password failed, err: %v", err)
}
global.LOG.Info("excute password change sql successful")
_ = mysqlRepo.Update(mysql.ID, map[string]interface{}{"password": info.Value})
_ = mysqlRepo.Update(mysqlData.ID, map[string]interface{}{"password": pass})
return nil
}
hosts, err := excuteSqlForRows(app.ContainerName, app.Password, "select host from mysql.user where user='root';")
if err != nil {
return err
}
for _, host := range hosts {
if host == "%" || host == "localhost" {
passwordRootChangeCMD := fmt.Sprintf("set password for 'root'@'%s' = password('%s')", host, info.Value)
if !strings.HasPrefix(app.Version, "5.7") && !strings.HasPrefix(app.Version, "5.6") {
passwordRootChangeCMD = fmt.Sprintf("alter user 'root'@'%s' identified with mysql_native_password BY '%s';", host, info.Value)
}
if err := excuteSql(app.ContainerName, app.Password, passwordRootChangeCMD); err != nil {
return err
}
}
}
if err := updateInstallInfoInDB("mysql", "", "password", false, info.Value); err != nil {
return err
}
if err := updateInstallInfoInDB("phpmyadmin", "", "password", true, info.Value); err != nil {
if err := updateInstallInfoInDB(req.Type, req.Database, "password", false, req.Value); err != nil {
return err
}
return nil
}
func (u *MysqlService) ChangeAccess(info dto.ChangeDBInfo) error {
func (u *MysqlService) ChangeAccess(req dto.ChangeDBInfo) error {
if cmd.CheckIllegal(req.Value) {
return buserr.New(constant.ErrCmdIllegal)
}
cli, version, err := LoadMysqlClientByFrom(req.Database)
if err != nil {
return err
}
defer cli.Close()
var (
mysql model.DatabaseMysql
err error
mysqlData model.DatabaseMysql
accessInfo client.AccessChangeInfo
)
if info.ID != 0 {
mysql, err = mysqlRepo.Get(commonRepo.WithByID(info.ID))
accessInfo.Permission = req.Value
accessInfo.Timeout = 300
accessInfo.Version = version
if req.ID != 0 {
mysqlData, err = mysqlRepo.Get(commonRepo.WithByID(req.ID))
if err != nil {
return err
}
if info.Value == mysql.Permission {
return nil
}
accessInfo.Name = mysqlData.Name
accessInfo.Username = mysqlData.Username
accessInfo.Password = mysqlData.Password
accessInfo.OldPermission = mysqlData.Permission
} else {
accessInfo.Username = "root"
}
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
if err != nil {
if err := cli.ChangeAccess(accessInfo); err != nil {
return err
}
if info.ID == 0 {
mysql.Name = "*"
mysql.Username = "root"
mysql.Permission = "%"
mysql.Password = app.Password
}
if info.Value != mysql.Permission {
var userlist []string
if strings.Contains(mysql.Permission, ",") {
userlist = strings.Split(mysql.Permission, ",")
} else {
userlist = append(userlist, mysql.Permission)
}
for _, user := range userlist {
if len(user) != 0 {
if strings.HasPrefix(app.Version, "5.6") {
if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("drop user '%s'@'%s'", mysql.Username, user)); err != nil {
return err
}
} else {
if err := excuteSql(app.ContainerName, app.Password, fmt.Sprintf("drop user if exists '%s'@'%s'", mysql.Username, user)); err != nil {
return err
}
}
}
}
if info.ID == 0 {
return nil
}
if mysqlData.ID != 0 {
_ = mysqlRepo.Update(mysqlData.ID, map[string]interface{}{"permission": req.Value})
}
if err := u.createUser(app.ContainerName, app.Password, app.Version, dto.MysqlDBCreate{
Username: mysql.Username,
Name: mysql.Name,
Permission: info.Value,
Password: mysql.Password,
}); err != nil {
return err
}
if err := excuteSql(app.ContainerName, app.Password, "flush privileges"); err != nil {
return err
}
if info.ID == 0 {
return nil
}
_ = mysqlRepo.Update(mysql.ID, map[string]interface{}{"permission": info.Value})
return nil
}
func (u *MysqlService) UpdateConfByFile(info dto.MysqlConfUpdateByFile) error {
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
func (u *MysqlService) UpdateConfByFile(req dto.MysqlConfUpdateByFile) error {
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database)
if err != nil {
return err
}
path := fmt.Sprintf("%s/mysql/%s/conf/my.cnf", constant.AppInstallDir, app.Name)
path := fmt.Sprintf("%s/%s/%s/conf/my.cnf", constant.AppInstallDir, req.Type, app.Name)
file, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0640)
if err != nil {
return err
}
defer file.Close()
write := bufio.NewWriter(file)
_, _ = write.WriteString(info.File)
_, _ = write.WriteString(req.File)
write.Flush()
if _, err := compose.Restart(fmt.Sprintf("%s/mysql/%s/docker-compose.yml", constant.AppInstallDir, app.Name)); err != nil {
if _, err := compose.Restart(fmt.Sprintf("%s/%s/%s/docker-compose.yml", constant.AppInstallDir, req.Type, app.Name)); err != nil {
return err
}
return nil
}
func (u *MysqlService) UpdateVariables(updates []dto.MysqlVariablesUpdate) error {
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
func (u *MysqlService) UpdateVariables(req dto.MysqlVariablesUpdate) error {
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database)
if err != nil {
return err
}
var files []string
path := fmt.Sprintf("%s/mysql/%s/conf/my.cnf", constant.AppInstallDir, app.Name)
path := fmt.Sprintf("%s/%s/%s/conf/my.cnf", constant.AppInstallDir, req.Type, app.Name)
lineBytes, err := os.ReadFile(path)
if err != nil {
return err
@@ -357,7 +404,7 @@ func (u *MysqlService) UpdateVariables(updates []dto.MysqlVariablesUpdate) error
files = strings.Split(string(lineBytes), "\n")
group := "[mysqld]"
for _, info := range updates {
for _, info := range req.Variables {
if !strings.HasPrefix(app.Version, "5.7") && !strings.HasPrefix(app.Version, "5.6") {
if info.Param == "query_cache_size" {
continue
@@ -380,16 +427,16 @@ func (u *MysqlService) UpdateVariables(updates []dto.MysqlVariablesUpdate) error
return err
}
if _, err := compose.Restart(fmt.Sprintf("%s/mysql/%s/docker-compose.yml", constant.AppInstallDir, app.Name)); err != nil {
if _, err := compose.Restart(fmt.Sprintf("%s/%s/%s/docker-compose.yml", constant.AppInstallDir, req.Type, app.Name)); err != nil {
return err
}
return nil
}
func (u *MysqlService) LoadBaseInfo() (*dto.DBBaseInfo, error) {
func (u *MysqlService) LoadBaseInfo(req dto.OperationWithNameAndType) (*dto.DBBaseInfo, error) {
var data dto.DBBaseInfo
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil {
return nil, err
}
@@ -400,8 +447,8 @@ func (u *MysqlService) LoadBaseInfo() (*dto.DBBaseInfo, error) {
return &data, nil
}
func (u *MysqlService) LoadRemoteAccess() (bool, error) {
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
func (u *MysqlService) LoadRemoteAccess(req dto.OperationWithNameAndType) (bool, error) {
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil {
return false, err
}
@@ -418,8 +465,8 @@ func (u *MysqlService) LoadRemoteAccess() (bool, error) {
return false, nil
}
func (u *MysqlService) LoadVariables() (*dto.MysqlVariables, error) {
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
func (u *MysqlService) LoadVariables(req dto.OperationWithNameAndType) (*dto.MysqlVariables, error) {
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil {
return nil, err
}
@@ -436,8 +483,8 @@ func (u *MysqlService) LoadVariables() (*dto.MysqlVariables, error) {
return &info, nil
}
func (u *MysqlService) LoadStatus() (*dto.MysqlStatus, error) {
app, err := appInstallRepo.LoadBaseInfo("mysql", "")
func (u *MysqlService) LoadStatus(req dto.OperationWithNameAndType) (*dto.MysqlStatus, error) {
app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil {
return nil, err
}
@@ -481,51 +528,28 @@ func (u *MysqlService) LoadStatus() (*dto.MysqlStatus, error) {
return &info, nil
}
func (u *MysqlService) createUser(container, password, version string, req dto.MysqlDBCreate) error {
var userlist []string
if strings.Contains(req.Permission, ",") {
ips := strings.Split(req.Permission, ",")
for _, ip := range ips {
if len(ip) != 0 {
userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", req.Username, ip))
}
}
} else {
userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", req.Username, req.Permission))
func (u *MysqlService) LoadDatabaseFile(req dto.OperationWithNameAndType) (string, error) {
filePath := ""
switch req.Type {
case "mysql-conf":
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/mysql/%s/conf/my.cnf", req.Name))
case "mariadb-conf":
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/mariadb/%s/conf/my.cnf", req.Name))
case "redis-conf":
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/redis/%s/conf/redis.conf", req.Name))
case "mysql-slow-logs":
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/mysql/%s/data/1Panel-slow.log", req.Name))
case "mariadb-slow-logs":
filePath = path.Join(global.CONF.System.DataDir, fmt.Sprintf("apps/mariadb/%s/db/data/1Panel-slow.log", req.Name))
}
for _, user := range userlist {
if err := excSQL(container, password, fmt.Sprintf("create user %s identified by '%s';", user, req.Password)); err != nil {
if strings.Contains(err.Error(), "ERROR 1396") {
handleCreateError(container, password, req.Name, userlist, false)
return buserr.New(constant.ErrUserIsExist)
}
handleCreateError(container, password, req.Name, userlist, true)
return err
}
grantStr := fmt.Sprintf("grant all privileges on `%s`.* to %s", req.Name, user)
if req.Name == "*" {
grantStr = fmt.Sprintf("grant all privileges on *.* to %s", user)
}
if strings.HasPrefix(version, "5.7") || strings.HasPrefix(version, "5.6") {
grantStr = fmt.Sprintf("%s identified by '%s' with grant option;", grantStr, req.Password)
}
if err := excSQL(container, password, grantStr); err != nil {
handleCreateError(container, password, req.Name, userlist, true)
return err
}
if _, err := os.Stat(filePath); err != nil {
return "", buserr.New("ErrHttpReqNotFound")
}
return nil
}
func handleCreateError(contaienr, password, dbName string, userlist []string, dropUser bool) {
_ = excSQL(contaienr, password, fmt.Sprintf("drop database `%s`", dbName))
if dropUser {
for _, user := range userlist {
if err := excSQL(contaienr, password, fmt.Sprintf("drop user if exists %s", user)); err != nil {
global.LOG.Errorf("drop user failed, err: %v", err)
}
}
content, err := os.ReadFile(filePath)
if err != nil {
return "", err
}
return string(content), nil
}
func excuteSqlForMaps(containerName, password, command string) (map[string]string, error) {
@@ -557,31 +581,6 @@ func excuteSqlForRows(containerName, password, command string) ([]string, error)
return strings.Split(stdStr, "\n"), nil
}
func excuteSql(containerName, password, command string) error {
cmd := exec.Command("docker", "exec", containerName, "mysql", "-uroot", "-p"+password, "-e", command)
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
}
func excSQL(containerName, password, command string) error {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "docker", "exec", containerName, "mysql", "-uroot", "-p"+password, "-e", command)
stdout, err := cmd.CombinedOutput()
if ctx.Err() == context.DeadlineExceeded {
return buserr.WithDetail(constant.ErrExecTimeOut, containerName, nil)
}
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
}
func updateMyCnf(oldFiles []string, group string, param string, value interface{}) []string {
isOn := false
hasGroup := false
@@ -622,3 +621,42 @@ func updateMyCnf(oldFiles []string, group string, param string, value interface{
}
return newFiles
}
func LoadMysqlClientByFrom(database string) (mysql.MysqlClient, string, error) {
var (
dbInfo client.DBInfo
version string
err error
)
dbInfo.Timeout = 300
databaseItem, err := databaseRepo.Get(commonRepo.WithByName(database))
if err != nil {
return nil, "", err
}
dbInfo.From = databaseItem.From
dbInfo.Database = database
if dbInfo.From != "local" {
dbInfo.Address = databaseItem.Address
dbInfo.Port = databaseItem.Port
dbInfo.Username = databaseItem.Username
dbInfo.Password = databaseItem.Password
version = databaseItem.Version
} else {
app, err := appInstallRepo.LoadBaseInfo(databaseItem.Type, database)
if err != nil {
return nil, "", err
}
dbInfo.Address = app.ContainerName
dbInfo.Username = "root"
dbInfo.Password = app.Password
version = app.Version
}
cli, err := mysql.NewMysqlClient(dbInfo)
if err != nil {
return nil, "", err
}
return cli, version, nil
}
+4 -3
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
@@ -20,7 +21,7 @@ type RedisService struct{}
type IRedisService interface {
UpdateConf(req dto.RedisConfUpdate) error
UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error
ChangePassword(info dto.ChangeDBInfo) error
ChangePassword(info dto.ChangeRedisPass) error
LoadStatus() (*dto.RedisStatus, error)
LoadConf() (*dto.RedisConf, error)
@@ -53,7 +54,7 @@ func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
return nil
}
func (u *RedisService) ChangePassword(req dto.ChangeDBInfo) error {
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
if err := updateInstallInfoInDB("redis", "", "password", true, req.Value); err != nil {
return err
}
@@ -163,7 +164,7 @@ func (u *RedisService) SearchBackupListWithPage(req dto.PageInfo) (int64, interf
if err != nil {
return 0, nil, err
}
backupDir := fmt.Sprintf("%s/database/redis/%s", localDir, redisInfo.Name)
backupDir := path.Join(localDir, fmt.Sprintf("database/redis/%s", redisInfo.Name))
_ = filepath.Walk(backupDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
+2 -2
View File
@@ -136,14 +136,14 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
switch req.Key {
case "Registries":
req.Value = strings.TrimRight(req.Value, ",")
req.Value = strings.TrimSuffix(req.Value, ",")
if len(req.Value) == 0 {
delete(daemonMap, "insecure-registries")
} else {
daemonMap["insecure-registries"] = strings.Split(req.Value, ",")
}
case "Mirrors":
req.Value = strings.TrimRight(req.Value, ",")
req.Value = strings.TrimSuffix(req.Value, ",")
if len(req.Value) == 0 {
delete(daemonMap, "registry-mirrors")
} else {
+2 -1
View File
@@ -12,7 +12,8 @@ var (
appInstallRepo = repo.NewIAppInstallRepo()
appInstallResourceRepo = repo.NewIAppInstallResourceRpo()
mysqlRepo = repo.NewIMysqlRepo()
mysqlRepo = repo.NewIMysqlRepo()
databaseRepo = repo.NewIDatabaseRepo()
imageRepoRepo = repo.NewIImageRepoRepo()
composeRepo = repo.NewIComposeTemplateRepo()
+229 -63
View File
@@ -5,14 +5,18 @@ import (
"os"
"strconv"
"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/common"
"github.com/1Panel-dev/1Panel/backend/utils/firewall"
fireClient "github.com/1Panel-dev/1Panel/backend/utils/firewall/client"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
)
const confPath = "/etc/sysctl.conf"
@@ -27,6 +31,7 @@ type IFirewallService interface {
OperateAddressRule(req dto.AddrRuleOperate, reload bool) error
UpdatePortRule(req dto.PortRuleUpdate) error
UpdateAddrRule(req dto.AddrRuleUpdate) error
UpdateDescription(req dto.UpdateFirewallDescription) error
BatchOperateRule(req dto.BatchRuleOperate) error
}
@@ -36,7 +41,6 @@ func NewIFirewallService() IFirewallService {
func (u *FirewallService) LoadBaseInfo() (dto.FirewallBaseInfo, error) {
var baseInfo dto.FirewallBaseInfo
baseInfo.PingStatus = u.pingStatus()
baseInfo.Status = "not running"
baseInfo.Version = "-"
baseInfo.Name = "-"
@@ -48,17 +52,22 @@ func (u *FirewallService) LoadBaseInfo() (dto.FirewallBaseInfo, error) {
return baseInfo, err
}
baseInfo.Name = client.Name()
baseInfo.Status, err = client.Status()
if err != nil {
return baseInfo, err
}
if baseInfo.Status == "not running" {
return baseInfo, err
}
baseInfo.Version, err = client.Version()
if err != nil {
return baseInfo, err
}
var wg sync.WaitGroup
wg.Add(3)
go func() {
defer wg.Done()
baseInfo.PingStatus = u.pingStatus()
}()
go func() {
defer wg.Done()
baseInfo.Status, _ = client.Status()
}()
go func() {
defer wg.Done()
baseInfo.Version, _ = client.Version()
}()
wg.Wait()
return baseInfo, nil
}
@@ -67,6 +76,7 @@ func (u *FirewallService) SearchWithPage(req dto.RuleSearch) (int64, interface{}
datas []fireClient.FireInfo
backDatas []fireClient.FireInfo
)
client, err := firewall.NewFirewallClient()
if err != nil {
return 0, nil, err
@@ -100,14 +110,61 @@ func (u *FirewallService) SearchWithPage(req dto.RuleSearch) (int64, interface{}
datas = addrs
}
}
total, start, end := len(datas), (req.Page-1)*req.PageSize, req.Page*req.PageSize
var datasFilterStatus []fireClient.FireInfo
if len(req.Status) != 0 {
for _, data := range datas {
portItem, _ := strconv.Atoi(data.Port)
if req.Status == "free" && !common.ScanPortWithProto(portItem, data.Protocol) {
datasFilterStatus = append(datasFilterStatus, data)
}
if req.Status == "used" && common.ScanPortWithProto(portItem, data.Protocol) {
datasFilterStatus = append(datasFilterStatus, data)
}
}
} else {
datasFilterStatus = datas
}
var datasFilterStrategy []fireClient.FireInfo
if len(req.Strategy) != 0 {
for _, data := range datasFilterStatus {
if req.Strategy == data.Strategy {
datasFilterStrategy = append(datasFilterStrategy, data)
}
}
} else {
datasFilterStrategy = datasFilterStatus
}
total, start, end := len(datasFilterStrategy), (req.Page-1)*req.PageSize, req.Page*req.PageSize
if start > total {
backDatas = make([]fireClient.FireInfo, 0)
} else {
if end >= total {
end = total
}
backDatas = datas[start:end]
backDatas = datasFilterStrategy[start:end]
}
datasFromDB, _ := hostRepo.ListFirewallRecord()
for i := 0; i < len(backDatas); i++ {
for _, des := range datasFromDB {
if req.Type != des.Type {
continue
}
if backDatas[i].Port == des.Port &&
req.Type == "port" &&
backDatas[i].Protocol == des.Protocol &&
backDatas[i].Strategy == des.Strategy &&
backDatas[i].Address == des.Address {
backDatas[i].Description = des.Description
break
}
if req.Type == "address" && backDatas[i].Strategy == des.Strategy && backDatas[i].Address == des.Address {
backDatas[i].Description = des.Description
break
}
}
}
if req.Type == "port" {
@@ -127,6 +184,7 @@ func (u *FirewallService) SearchWithPage(req dto.RuleSearch) (int64, interface{}
}
}
}
go u.cleanUnUsedData(client)
return int64(total), backDatas, nil
}
@@ -166,55 +224,92 @@ func (u *FirewallService) OperatePortRule(req dto.PortRuleOperate, reload bool)
if err != nil {
return err
}
protos := strings.Split(req.Protocol, "/")
itemAddress := strings.Split(strings.TrimSuffix(req.Address, ","), ",")
var wg sync.WaitGroup
if client.Name() == "ufw" {
req.Port = strings.ReplaceAll(req.Port, "-", ":")
if req.Operation == "remove" && req.Protocol == "tcp/udp" {
req.Protocol = ""
return u.operatePort(client, req)
if strings.Contains(req.Port, ",") || strings.Contains(req.Port, "-") {
for _, proto := range protos {
for _, addr := range itemAddress {
if len(addr) == 0 {
addr = "Anywhere"
}
req.Address = addr
req.Port = strings.ReplaceAll(req.Port, "-", ":")
req.Protocol = proto
wg.Add(1)
go func(req dto.PortRuleOperate) {
defer wg.Done()
if err := u.operatePort(client, req); err != nil {
global.LOG.Errorf("%s port %s/%s failed (strategy: %s, address: %s), err: %v", req.Operation, req.Port, req.Protocol, req.Strategy, req.Address, err)
}
_ = u.addPortRecord(req)
}(req)
}
}
return nil
}
if req.Protocol == "tcp/udp" {
req.Protocol = ""
}
for _, addr := range itemAddress {
if len(addr) == 0 {
addr = "Anywhere"
}
req.Address = addr
wg.Add(1)
go func(req dto.PortRuleOperate) {
defer wg.Done()
if err := u.operatePort(client, req); err != nil {
global.LOG.Errorf("%s port %s/%s failed (strategy: %s, address: %s), err: %v", req.Operation, req.Port, req.Protocol, req.Strategy, req.Address, err)
}
_ = u.addPortRecord(req)
}(req)
}
wg.Wait()
return nil
}
if req.Protocol == "tcp/udp" {
if client.Name() == "firewalld" && strings.Contains(req.Port, ",") {
ports := strings.Split(req.Port, ",")
itemPorts := req.Port
for _, proto := range protos {
if strings.Contains(req.Port, "-") {
for _, addr := range itemAddress {
req.Protocol = proto
req.Address = addr
wg.Add(1)
go func(req dto.PortRuleOperate) {
defer wg.Done()
if err := u.operatePort(client, req); err != nil {
global.LOG.Errorf("%s port %s/%s failed (strategy: %s, address: %s), err: %v", req.Operation, req.Port, req.Protocol, req.Strategy, req.Address, err)
}
_ = u.addPortRecord(req)
}(req)
}
} else {
ports := strings.Split(itemPorts, ",")
for _, port := range ports {
if len(port) == 0 {
continue
}
req.Port = port
req.Protocol = "tcp"
if err := u.operatePort(client, req); err != nil {
return err
for _, addr := range itemAddress {
req.Address = addr
req.Port = port
req.Protocol = proto
wg.Add(1)
go func(req dto.PortRuleOperate) {
defer wg.Done()
if err := u.operatePort(client, req); err != nil {
global.LOG.Errorf("%s port %s/%s failed (strategy: %s, address: %s), err: %v", req.Operation, req.Port, req.Protocol, req.Strategy, req.Address, err)
}
_ = u.addPortRecord(req)
}(req)
}
req.Protocol = "udp"
if err := u.operatePort(client, req); err != nil {
return err
}
}
} else {
req.Protocol = "tcp"
if err := u.operatePort(client, req); err != nil {
return err
}
req.Protocol = "udp"
if err := u.operatePort(client, req); err != nil {
return err
}
}
} else {
if strings.Contains(req.Port, ",") {
ports := strings.Split(req.Port, ",")
for _, port := range ports {
req.Port = port
if err := u.operatePort(client, req); err != nil {
return err
}
}
} else {
if err := u.operatePort(client, req); err != nil {
return err
}
}
}
wg.Wait()
if reload {
return client.Reload()
}
@@ -232,16 +327,24 @@ func (u *FirewallService) OperateAddressRule(req dto.AddrRuleOperate, reload boo
return err
}
var wg sync.WaitGroup
addressList := strings.Split(req.Address, ",")
for _, addr := range addressList {
if len(addr) == 0 {
continue
}
fireInfo.Address = addr
if err := client.RichRules(fireInfo, req.Operation); err != nil {
return err
}
wg.Add(1)
go func(req dto.AddrRuleOperate) {
defer wg.Done()
if err := client.RichRules(fireInfo, req.Operation); err != nil {
global.LOG.Errorf("%s address %s failed (strategy: %s), err: %v", req.Operation, req.Address, req.Strategy, err)
}
req.Address = fireInfo.Address
_ = u.addAddressRecord(req)
}(req)
}
wg.Wait()
if reload {
return client.Reload()
}
@@ -276,24 +379,38 @@ func (u *FirewallService) UpdateAddrRule(req dto.AddrRuleUpdate) error {
return client.Reload()
}
func (u *FirewallService) UpdateDescription(req dto.UpdateFirewallDescription) error {
var firewall model.Firewall
if err := copier.Copy(&firewall, &req); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
}
return hostRepo.SaveFirewallRecord(&firewall)
}
func (u *FirewallService) BatchOperateRule(req dto.BatchRuleOperate) error {
client, err := firewall.NewFirewallClient()
if err != nil {
return err
}
var wgBatch sync.WaitGroup
if req.Type == "port" {
for _, rule := range req.Rules {
if err := u.OperatePortRule(rule, false); err != nil {
return err
}
wgBatch.Add(1)
go func(item dto.PortRuleOperate) {
defer wgBatch.Done()
_ = u.OperatePortRule(item, false)
}(rule)
}
wgBatch.Wait()
return client.Reload()
}
for _, rule := range req.Rules {
itemRule := dto.AddrRuleOperate{Operation: rule.Operation, Address: rule.Address, Strategy: rule.Strategy}
if err := u.OperateAddressRule(itemRule, false); err != nil {
return err
}
wgBatch.Add(1)
go func(item dto.AddrRuleOperate) {
defer wgBatch.Done()
_ = u.OperateAddressRule(item, false)
}(itemRule)
}
return client.Reload()
}
@@ -304,7 +421,6 @@ func OperateFirewallPort(oldPorts, newPorts []int) error {
return err
}
for _, port := range newPorts {
if err := client.Port(fireClient.FireInfo{Port: strconv.Itoa(port), Protocol: "tcp", Strategy: "accept"}, "add"); err != nil {
return err
}
@@ -324,7 +440,7 @@ func (u *FirewallService) operatePort(client firewall.FirewallClient, req dto.Po
}
if client.Name() == "ufw" {
if len(fireInfo.Address) != 0 && fireInfo.Address != "Anywhere" {
if len(fireInfo.Address) != 0 && !strings.EqualFold(fireInfo.Address, "Anywhere") {
return client.RichRules(fireInfo, req.Operation)
}
return client.Port(fireInfo, req.Operation)
@@ -364,6 +480,29 @@ func (u *FirewallService) loadPortByApp() []portOfApp {
return datas
}
func (u *FirewallService) cleanUnUsedData(client firewall.FirewallClient) {
list, _ := client.ListPort()
addressList, _ := client.ListAddress()
list = append(list, addressList...)
if len(list) == 0 {
return
}
records, _ := hostRepo.ListFirewallRecord()
if len(records) == 0 {
return
}
for _, item := range list {
for i := 0; i < len(records); i++ {
if records[i].Port == item.Port && records[i].Protocol == item.Protocol && records[i].Strategy == item.Strategy && records[i].Address == item.Address {
records = append(records[:i], records[i+1:]...)
}
}
}
for _, record := range records {
_ = hostRepo.DeleteFirewallRecordByID(record.ID)
}
}
func (u *FirewallService) pingStatus() string {
if _, err := os.Stat("/etc/sysctl.conf"); err != nil {
return constant.StatusNone
@@ -442,3 +581,30 @@ func (u *FirewallService) addPortsBeforeStart(client firewall.FirewallClient) er
return client.Reload()
}
func (u *FirewallService) addPortRecord(req dto.PortRuleOperate) error {
if req.Operation == "remove" {
return hostRepo.DeleteFirewallRecord("port", req.Port, req.Protocol, req.Address, req.Strategy)
}
return hostRepo.SaveFirewallRecord(&model.Firewall{
Type: "port",
Port: req.Port,
Protocol: req.Protocol,
Address: req.Address,
Strategy: req.Strategy,
Description: req.Description,
})
}
func (u *FirewallService) addAddressRecord(req dto.AddrRuleOperate) error {
if req.Operation == "remove" {
return hostRepo.DeleteFirewallRecord("address", "", "", req.Address, req.Strategy)
}
return hostRepo.SaveFirewallRecord(&model.Firewall{
Type: "address",
Address: req.Address,
Strategy: req.Strategy,
Description: req.Description,
})
}
+33
View File
@@ -24,6 +24,8 @@ type IHostService interface {
Create(hostDto dto.HostOperate) (*dto.HostInfo, error)
Update(id uint, upMap map[string]interface{}) error
Delete(id []uint) error
EncryptHost(itemVal string) (string, error)
}
func NewIHostService() IHostService {
@@ -220,6 +222,28 @@ func (u *HostService) SearchForTree(search dto.SearchForTree) ([]dto.HostTree, e
}
func (u *HostService) Create(req dto.HostOperate) (*dto.HostInfo, error) {
var err error
if len(req.Password) != 0 && req.AuthMode == "password" {
req.Password, err = u.EncryptHost(req.Password)
if err != nil {
return nil, err
}
req.PrivateKey = ""
req.PassPhrase = ""
}
if len(req.PrivateKey) != 0 && req.AuthMode == "key" {
req.PrivateKey, err = u.EncryptHost(req.PrivateKey)
if err != nil {
return nil, err
}
if len(req.PassPhrase) != 0 {
req.PassPhrase, err = encrypt.StringEncrypt(req.PassPhrase)
if err != nil {
return nil, err
}
}
req.Password = ""
}
var host model.Host
if err := copier.Copy(&host, &req); err != nil {
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
@@ -290,3 +314,12 @@ func (u *HostService) Delete(ids []uint) error {
func (u *HostService) Update(id uint, upMap map[string]interface{}) error {
return hostRepo.Update(id, upMap)
}
func (u *HostService) EncryptHost(itemVal string) (string, error) {
privateKey, err := base64.StdEncoding.DecodeString(itemVal)
if err != nil {
return "", err
}
keyItem, err := encrypt.StringEncrypt(string(privateKey))
return keyItem, err
}
+565
View File
@@ -0,0 +1,565 @@
package service
import (
"bytes"
"fmt"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
"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/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/1Panel-dev/1Panel/backend/utils/ini_conf"
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
"github.com/pkg/errors"
"gopkg.in/ini.v1"
"os/exec"
"os/user"
"path"
"strconv"
"strings"
)
type HostToolService struct{}
type IHostToolService interface {
GetToolStatus(req request.HostToolReq) (*response.HostToolRes, error)
CreateToolConfig(req request.HostToolCreate) error
OperateTool(req request.HostToolReq) error
OperateToolConfig(req request.HostToolConfig) (*response.HostToolConfig, error)
GetToolLog(req request.HostToolLogReq) (string, error)
OperateSupervisorProcess(req request.SupervisorProcessConfig) error
GetSupervisorProcessConfig() ([]response.SupervisorProcessConfig, error)
OperateSupervisorProcessFile(req request.SupervisorProcessFileReq) (string, error)
}
func NewIHostToolService() IHostToolService {
return &HostToolService{}
}
func (h *HostToolService) GetToolStatus(req request.HostToolReq) (*response.HostToolRes, error) {
res := &response.HostToolRes{}
res.Type = req.Type
switch req.Type {
case constant.Supervisord:
supervisorConfig := &response.Supervisor{}
if !cmd.Which(constant.Supervisord) {
supervisorConfig.IsExist = false
res.Config = supervisorConfig
return res, nil
}
supervisorConfig.IsExist = true
serviceExist, _ := systemctl.IsExist(constant.Supervisord)
if !serviceExist {
serviceExist, _ = systemctl.IsExist(constant.Supervisor)
if !serviceExist {
supervisorConfig.IsExist = false
res.Config = supervisorConfig
return res, nil
} else {
supervisorConfig.ServiceName = constant.Supervisor
}
} else {
supervisorConfig.ServiceName = constant.Supervisord
}
serviceNameSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName))
if serviceNameSet.ID != 0 || serviceNameSet.Value != "" {
supervisorConfig.ServiceName = serviceNameSet.Value
}
versionRes, _ := cmd.Exec("supervisord -v")
supervisorConfig.Version = strings.TrimSuffix(versionRes, "\n")
_, ctlRrr := exec.LookPath("supervisorctl")
supervisorConfig.CtlExist = ctlRrr == nil
active, _ := systemctl.IsActive(supervisorConfig.ServiceName)
if active {
supervisorConfig.Status = "running"
} else {
supervisorConfig.Status = "stopped"
}
pathSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath))
if pathSet.ID != 0 || pathSet.Value != "" {
supervisorConfig.ConfigPath = pathSet.Value
res.Config = supervisorConfig
return res, nil
} else {
supervisorConfig.Init = true
}
servicePath := "/usr/lib/systemd/system/supervisor.service"
fileOp := files.NewFileOp()
if !fileOp.Stat(servicePath) {
servicePath = "/usr/lib/systemd/system/supervisord.service"
}
if fileOp.Stat(servicePath) {
startCmd, _ := ini_conf.GetIniValue(servicePath, "Service", "ExecStart")
if startCmd != "" {
args := strings.Fields(startCmd)
cIndex := -1
for i, arg := range args {
if arg == "-c" {
cIndex = i
break
}
}
if cIndex != -1 && cIndex+1 < len(args) {
supervisorConfig.ConfigPath = args[cIndex+1]
}
}
}
if supervisorConfig.ConfigPath == "" {
configPath := "/etc/supervisord.conf"
if !fileOp.Stat(configPath) {
configPath = "/etc/supervisor/supervisord.conf"
if fileOp.Stat(configPath) {
supervisorConfig.ConfigPath = configPath
}
}
}
res.Config = supervisorConfig
}
return res, nil
}
func (h *HostToolService) CreateToolConfig(req request.HostToolCreate) error {
switch req.Type {
case constant.Supervisord:
fileOp := files.NewFileOp()
if !fileOp.Stat(req.ConfigPath) {
return buserr.New("ErrConfigNotFound")
}
cfg, err := ini.Load(req.ConfigPath)
if err != nil {
return err
}
service, err := cfg.GetSection("include")
if err != nil {
return err
}
targetKey, err := service.GetKey("files")
if err != nil {
return err
}
if targetKey != nil {
_, err = service.NewKey(";files", targetKey.Value())
if err != nil {
return err
}
}
supervisorDir := path.Join(global.CONF.System.BaseDir, "1panel", "tools", "supervisord")
includeDir := path.Join(supervisorDir, "supervisor.d")
if !fileOp.Stat(includeDir) {
if err = fileOp.CreateDir(includeDir, 0755); err != nil {
return err
}
}
logDir := path.Join(supervisorDir, "log")
if !fileOp.Stat(logDir) {
if err = fileOp.CreateDir(logDir, 0755); err != nil {
return err
}
}
includePath := path.Join(includeDir, "*.ini")
targetKey.SetValue(includePath)
if err = cfg.SaveTo(req.ConfigPath); err != nil {
return err
}
serviceNameSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName))
if serviceNameSet.ID != 0 {
if err = settingRepo.Update(constant.SupervisorServiceName, req.ServiceName); err != nil {
return err
}
} else {
if err = settingRepo.Create(constant.SupervisorServiceName, req.ServiceName); err != nil {
return err
}
}
configPathSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath))
if configPathSet.ID != 0 {
if err = settingRepo.Update(constant.SupervisorConfigPath, req.ConfigPath); err != nil {
return err
}
} else {
if err = settingRepo.Create(constant.SupervisorConfigPath, req.ConfigPath); err != nil {
return err
}
}
if err = systemctl.Restart(req.ServiceName); err != nil {
global.LOG.Errorf("[init] restart %s failed err %s", req.ServiceName, err.Error())
return err
}
}
return nil
}
func (h *HostToolService) OperateTool(req request.HostToolReq) error {
serviceName := req.Type
if req.Type == constant.Supervisord {
serviceNameSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName))
if serviceNameSet.ID != 0 || serviceNameSet.Value != "" {
serviceName = serviceNameSet.Value
}
}
return systemctl.Operate(req.Operate, serviceName)
}
func (h *HostToolService) OperateToolConfig(req request.HostToolConfig) (*response.HostToolConfig, error) {
fileOp := files.NewFileOp()
res := &response.HostToolConfig{}
configPath := ""
serviceName := "supervisord"
switch req.Type {
case constant.Supervisord:
pathSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath))
if pathSet.ID != 0 || pathSet.Value != "" {
configPath = pathSet.Value
}
serviceNameSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName))
if serviceNameSet.ID != 0 || serviceNameSet.Value != "" {
serviceName = serviceNameSet.Value
}
}
switch req.Operate {
case "get":
content, err := fileOp.GetContent(configPath)
if err != nil {
return nil, err
}
res.Content = string(content)
case "set":
file, err := fileOp.OpenFile(configPath)
if err != nil {
return nil, err
}
oldContent, err := fileOp.GetContent(configPath)
if err != nil {
return nil, err
}
fileInfo, err := file.Stat()
if err != nil {
return nil, err
}
if err = fileOp.WriteFile(configPath, strings.NewReader(req.Content), fileInfo.Mode()); err != nil {
return nil, err
}
if err = systemctl.Restart(serviceName); err != nil {
_ = fileOp.WriteFile(configPath, bytes.NewReader(oldContent), fileInfo.Mode())
return nil, err
}
}
return res, nil
}
func (h *HostToolService) GetToolLog(req request.HostToolLogReq) (string, error) {
fileOp := files.NewFileOp()
logfilePath := ""
switch req.Type {
case constant.Supervisord:
configPath := "/etc/supervisord.conf"
pathSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath))
if pathSet.ID != 0 || pathSet.Value != "" {
configPath = pathSet.Value
}
logfilePath, _ = ini_conf.GetIniValue(configPath, "supervisord", "logfile")
}
oldContent, err := fileOp.GetContent(logfilePath)
if err != nil {
return "", err
}
return string(oldContent), nil
}
func (h *HostToolService) OperateSupervisorProcess(req request.SupervisorProcessConfig) error {
var (
supervisordDir = path.Join(global.CONF.System.BaseDir, "1panel", "tools", "supervisord")
logDir = path.Join(supervisordDir, "log")
includeDir = path.Join(supervisordDir, "supervisor.d")
outLog = path.Join(logDir, fmt.Sprintf("%s.out.log", req.Name))
errLog = path.Join(logDir, fmt.Sprintf("%s.err.log", req.Name))
iniPath = path.Join(includeDir, fmt.Sprintf("%s.ini", req.Name))
fileOp = files.NewFileOp()
)
if req.Operate == "update" || req.Operate == "create" {
if !fileOp.Stat(req.Dir) {
return buserr.New("ErrConfigDirNotFound")
}
_, err := user.Lookup(req.User)
if err != nil {
return buserr.WithMap("ErrUserFindErr", map[string]interface{}{"name": req.User, "err": err.Error()}, err)
}
}
switch req.Operate {
case "create":
if fileOp.Stat(iniPath) {
return buserr.New("ErrConfigAlreadyExist")
}
configFile := ini.Empty()
section, err := configFile.NewSection(fmt.Sprintf("program:%s", req.Name))
if err != nil {
return err
}
_, _ = section.NewKey("command", req.Command)
_, _ = section.NewKey("directory", req.Dir)
_, _ = section.NewKey("autorestart", "true")
_, _ = section.NewKey("startsecs", "3")
_, _ = section.NewKey("stdout_logfile", outLog)
_, _ = section.NewKey("stderr_logfile", errLog)
_, _ = section.NewKey("stdout_logfile_maxbytes", "2MB")
_, _ = section.NewKey("stderr_logfile_maxbytes", "2MB")
_, _ = section.NewKey("user", req.User)
_, _ = section.NewKey("priority", "999")
_, _ = section.NewKey("numprocs", req.Numprocs)
_, _ = section.NewKey("process_name", "%(program_name)s_%(process_num)02d")
if err = configFile.SaveTo(iniPath); err != nil {
return err
}
if err := operateSupervisorCtl("reread", "", ""); err != nil {
return err
}
return operateSupervisorCtl("update", "", "")
case "update":
configFile, err := ini.Load(iniPath)
if err != nil {
return err
}
section, err := configFile.GetSection(fmt.Sprintf("program:%s", req.Name))
if err != nil {
return err
}
commandKey := section.Key("command")
commandKey.SetValue(req.Command)
directoryKey := section.Key("directory")
directoryKey.SetValue(req.Dir)
userKey := section.Key("user")
userKey.SetValue(req.User)
numprocsKey := section.Key("numprocs")
numprocsKey.SetValue(req.Numprocs)
if err = configFile.SaveTo(iniPath); err != nil {
return err
}
if err := operateSupervisorCtl("reread", "", ""); err != nil {
return err
}
return operateSupervisorCtl("update", "", "")
case "restart":
return operateSupervisorCtl("restart", req.Name, "")
case "start":
return operateSupervisorCtl("start", req.Name, "")
case "stop":
return operateSupervisorCtl("stop", req.Name, "")
case "delete":
_ = operateSupervisorCtl("remove", "", req.Name)
_ = files.NewFileOp().DeleteFile(iniPath)
_ = files.NewFileOp().DeleteFile(outLog)
_ = files.NewFileOp().DeleteFile(errLog)
if err := operateSupervisorCtl("reread", "", ""); err != nil {
return err
}
return operateSupervisorCtl("update", "", "")
}
return nil
}
func (h *HostToolService) GetSupervisorProcessConfig() ([]response.SupervisorProcessConfig, error) {
var (
result []response.SupervisorProcessConfig
)
configDir := path.Join(global.CONF.System.BaseDir, "1panel", "tools", "supervisord", "supervisor.d")
fileList, _ := NewIFileService().GetFileList(request.FileOption{FileOption: files.FileOption{Path: configDir, Expand: true, Page: 1, PageSize: 100}})
if len(fileList.Items) == 0 {
return result, nil
}
for _, configFile := range fileList.Items {
f, err := ini.Load(configFile.Path)
if err != nil {
global.LOG.Errorf("get %s file err %s", configFile.Name, err.Error())
continue
}
if strings.HasSuffix(configFile.Name, ".ini") {
config := response.SupervisorProcessConfig{}
name := strings.TrimSuffix(configFile.Name, ".ini")
config.Name = name
section, err := f.GetSection(fmt.Sprintf("program:%s", name))
if err != nil {
global.LOG.Errorf("get %s file section err %s", configFile.Name, err.Error())
continue
}
if command, _ := section.GetKey("command"); command != nil {
config.Command = command.Value()
}
if directory, _ := section.GetKey("directory"); directory != nil {
config.Dir = directory.Value()
}
if user, _ := section.GetKey("user"); user != nil {
config.User = user.Value()
}
if numprocs, _ := section.GetKey("numprocs"); numprocs != nil {
config.Numprocs = numprocs.Value()
}
_ = getProcessStatus(&config)
result = append(result, config)
}
}
return result, nil
}
func (h *HostToolService) OperateSupervisorProcessFile(req request.SupervisorProcessFileReq) (string, error) {
var (
fileOp = files.NewFileOp()
group = fmt.Sprintf("program:%s", req.Name)
configPath = path.Join(global.CONF.System.BaseDir, "1panel", "tools", "supervisord", "supervisor.d", fmt.Sprintf("%s.ini", req.Name))
)
switch req.File {
case "err.log":
logPath, err := ini_conf.GetIniValue(configPath, group, "stderr_logfile")
if err != nil {
return "", err
}
switch req.Operate {
case "get":
content, err := fileOp.GetContent(logPath)
if err != nil {
return "", err
}
return string(content), nil
case "clear":
if err = fileOp.WriteFile(logPath, strings.NewReader(""), 0755); err != nil {
return "", err
}
}
case "out.log":
logPath, err := ini_conf.GetIniValue(configPath, group, "stdout_logfile")
if err != nil {
return "", err
}
switch req.Operate {
case "get":
content, err := fileOp.GetContent(logPath)
if err != nil {
return "", err
}
return string(content), nil
case "clear":
if err = fileOp.WriteFile(logPath, strings.NewReader(""), 0755); err != nil {
return "", err
}
}
case "config":
switch req.Operate {
case "get":
content, err := fileOp.GetContent(configPath)
if err != nil {
return "", err
}
return string(content), nil
case "update":
if req.Content == "" {
return "", buserr.New("ErrConfigIsNull")
}
if err := fileOp.WriteFile(configPath, strings.NewReader(req.Content), 0755); err != nil {
return "", err
}
return "", operateSupervisorCtl("update", "", req.Name)
}
}
return "", nil
}
func operateSupervisorCtl(operate, name, group string) error {
processNames := []string{operate}
if name != "" {
includeDir := path.Join(global.CONF.System.BaseDir, "1panel", "tools", "supervisord", "supervisor.d")
f, err := ini.Load(path.Join(includeDir, fmt.Sprintf("%s.ini", name)))
if err != nil {
return err
}
section, err := f.GetSection(fmt.Sprintf("program:%s", name))
if err != nil {
return err
}
numprocsNum := ""
if numprocs, _ := section.GetKey("numprocs"); numprocs != nil {
numprocsNum = numprocs.Value()
}
if numprocsNum == "" {
return buserr.New("ErrConfigParse")
}
processNames = append(processNames, getProcessName(name, numprocsNum)...)
}
if group != "" {
processNames = append(processNames, group)
}
output, err := exec.Command("supervisorctl", processNames...).Output()
if err != nil {
if output != nil {
return errors.New(string(output))
}
return err
}
return nil
}
func getProcessName(name, numprocs string) []string {
var (
processNames []string
)
num, err := strconv.Atoi(numprocs)
if err != nil {
return processNames
}
if num == 1 {
processNames = append(processNames, fmt.Sprintf("%s:%s_00", name, name))
} else {
for i := 0; i < num; i++ {
processName := fmt.Sprintf("%s:%s_0%s", name, name, strconv.Itoa(i))
if i >= 10 {
processName = fmt.Sprintf("%s:%s_%s", name, name, strconv.Itoa(i))
}
processNames = append(processNames, processName)
}
}
return processNames
}
func getProcessStatus(config *response.SupervisorProcessConfig) error {
var (
processNames = []string{"status"}
)
processNames = append(processNames, getProcessName(config.Name, config.Numprocs)...)
output, _ := exec.Command("supervisorctl", processNames...).Output()
lines := strings.Split(string(output), "\n")
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) >= 5 {
status := response.ProcessStatus{
Name: fields[0],
Status: fields[1],
}
if fields[1] == "RUNNING" {
status.PID = strings.TrimSuffix(fields[3], ",")
status.Uptime = fields[5]
} else {
status.Msg = strings.Join(fields[2:], " ")
}
config.Status = append(config.Status, status)
}
}
return nil
}
+17 -12
View File
@@ -156,10 +156,15 @@ func (u *ImageService) ImageBuild(req dto.ImageBuild) (string, error) {
Remove: true,
Labels: stringsToMap(req.Tags),
}
logName := fmt.Sprintf("%s/build.log", req.Dockerfile)
pathItem := logName
file, err := os.OpenFile(pathItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
dockerLogDir := path.Join(global.CONF.System.TmpDir, "docker_logs")
if _, err := os.Stat(dockerLogDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(dockerLogDir, os.ModePerm); err != nil {
return "", err
}
}
logItem := fmt.Sprintf("%s/image_build_%s_%s.log", dockerLogDir, strings.ReplaceAll(req.Name, ":", "_"), time.Now().Format("20060102150405"))
file, err := os.OpenFile(logItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return "", err
}
@@ -192,7 +197,7 @@ func (u *ImageService) ImageBuild(req dto.ImageBuild) (string, error) {
_, _ = file.WriteString("image build successful!")
}()
return logName, nil
return path.Base(logItem), nil
}
func (u *ImageService) ImagePull(req dto.ImagePull) (string, error) {
@@ -200,15 +205,15 @@ func (u *ImageService) ImagePull(req dto.ImagePull) (string, error) {
if err != nil {
return "", err
}
dockerLogDir := global.CONF.System.TmpDir + "/docker_logs"
dockerLogDir := path.Join(global.CONF.System.TmpDir, "docker_logs")
if _, err := os.Stat(dockerLogDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(dockerLogDir, os.ModePerm); err != nil {
return "", err
}
}
imageItemName := strings.ReplaceAll(path.Base(req.ImageName), ":", "_")
pathItem := fmt.Sprintf("%s/image_pull_%s_%s.log", dockerLogDir, imageItemName, time.Now().Format("20060102150405"))
file, err := os.OpenFile(pathItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
logItem := fmt.Sprintf("%s/image_pull_%s_%s.log", dockerLogDir, imageItemName, time.Now().Format("20060102150405"))
file, err := os.OpenFile(logItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return "", err
}
@@ -224,7 +229,7 @@ func (u *ImageService) ImagePull(req dto.ImagePull) (string, error) {
global.LOG.Infof("pull image %s successful!", req.ImageName)
_, _ = io.Copy(file, out)
}()
return pathItem, nil
return path.Base(logItem), nil
}
repo, err := imageRepoRepo.Get(commonRepo.WithByID(req.RepoID))
if err != nil {
@@ -257,7 +262,7 @@ func (u *ImageService) ImagePull(req dto.ImagePull) (string, error) {
_, _ = io.Copy(file, out)
_, _ = file.WriteString("image pull successful!")
}()
return pathItem, nil
return path.Base(logItem), nil
}
func (u *ImageService) ImageLoad(req dto.ImageLoad) error {
@@ -354,8 +359,8 @@ func (u *ImageService) ImagePush(req dto.ImagePush) (string, error) {
}
}
imageItemName := strings.ReplaceAll(path.Base(req.Name), ":", "_")
pathItem := fmt.Sprintf("%s/image_push_%s_%s.log", dockerLogDir, imageItemName, time.Now().Format("20060102150405"))
file, err := os.OpenFile(pathItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
logItem := fmt.Sprintf("%s/image_push_%s_%s.log", dockerLogDir, imageItemName, time.Now().Format("20060102150405"))
file, err := os.OpenFile(logItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return "", err
}
@@ -373,7 +378,7 @@ func (u *ImageService) ImagePush(req dto.ImagePush) (string, error) {
_, _ = file.WriteString("image push successful!")
}()
return pathItem, nil
return path.Base(logItem), nil
}
func (u *ImageService) ImageRemove(req dto.BatchDelete) error {
+2 -2
View File
@@ -79,7 +79,7 @@ func (u *ImageRepoService) List() ([]dto.ImageRepoOption, error) {
func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
if cmd.CheckIllegal(req.Username, req.Password, req.DownloadUrl) {
return buserr.New(constant.ErrRepoConn)
return buserr.New(constant.ErrCmdIllegal)
}
imageRepo, _ := imageRepoRepo.Get(commonRepo.WithByName(req.Name))
if imageRepo.ID != 0 {
@@ -148,7 +148,7 @@ func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error {
return errors.New("The default value cannot be deleted !")
}
if cmd.CheckIllegal(req.Username, req.Password, req.DownloadUrl) {
return buserr.New(constant.ErrRepoConn)
return buserr.New(constant.ErrCmdIllegal)
}
repo, err := imageRepoRepo.Get(commonRepo.WithByID(req.ID))
if err != nil {
+19
View File
@@ -1,9 +1,14 @@
package service
import (
"os"
"path"
"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/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
@@ -20,6 +25,8 @@ type ILogService interface {
CreateOperationLog(operation model.OperationLog) error
PageOperationLog(search dto.SearchOpLogWithPage) (int64, interface{}, error)
LoadSystemLog() (string, error)
CleanLogs(logtype string) error
}
@@ -74,6 +81,18 @@ func (u *LogService) PageOperationLog(req dto.SearchOpLogWithPage) (int64, inter
return total, dtoOps, err
}
func (u *LogService) LoadSystemLog() (string, error) {
filePath := path.Join(global.CONF.System.DataDir, "log/1Panel.log")
if _, err := os.Stat(filePath); err != nil {
return "", buserr.New("ErrHttpReqNotFound")
}
content, err := os.ReadFile(filePath)
if err != nil {
return "", err
}
return string(content), nil
}
func (u *LogService) CleanLogs(logtype string) error {
if logtype == "operation" {
return logRepo.CleanOperation()
+13 -19
View File
@@ -76,29 +76,29 @@ func loadDiskIO() {
if io2.Name == io1.Name {
var itemIO model.MonitorIO
itemIO.Name = io1.Name
if io2.ReadBytes != 0 && io1.ReadBytes != 0 {
if io2.ReadBytes != 0 && io1.ReadBytes != 0 && io2.ReadBytes > io1.ReadBytes {
itemIO.Read = uint64(float64(io2.ReadBytes-io1.ReadBytes) / 60)
}
if io2.WriteBytes != 0 && io1.WriteBytes != 0 {
if io2.WriteBytes != 0 && io1.WriteBytes != 0 && io2.WriteBytes > io1.WriteBytes {
itemIO.Write = uint64(float64(io2.WriteBytes-io1.WriteBytes) / 60)
}
if io2.ReadCount != 0 && io1.ReadCount != 0 {
if io2.ReadCount != 0 && io1.ReadCount != 0 && io2.ReadCount > io1.ReadCount {
itemIO.Count = uint64(float64(io2.ReadCount-io1.ReadCount) / 60)
}
writeCount := uint64(0)
if io2.WriteCount != 0 && io1.WriteCount != 0 {
if io2.WriteCount != 0 && io1.WriteCount != 0 && io2.WriteCount > io1.WriteCount {
writeCount = uint64(float64(io2.WriteCount-io1.WriteCount) / 60)
}
if writeCount > itemIO.Count {
itemIO.Count = writeCount
}
if io2.ReadTime != 0 && io1.ReadTime != 0 {
if io2.ReadTime != 0 && io1.ReadTime != 0 && io2.ReadTime > io1.ReadTime {
itemIO.Time = uint64(float64(io2.ReadTime-io1.ReadTime) / 60)
}
writeTime := uint64(0)
if io2.WriteTime != 0 && io1.WriteTime != 0 {
if io2.WriteTime != 0 && io1.WriteTime != 0 && io2.WriteTime > io1.WriteTime {
writeTime = uint64(float64(io2.WriteTime-io1.WriteTime) / 60)
}
if writeTime > itemIO.Time {
@@ -128,10 +128,10 @@ func loadNetIO() {
var itemNet model.MonitorNetwork
itemNet.Name = net1.Name
if net2.BytesSent != 0 && net1.BytesSent != 0 {
if net2.BytesSent != 0 && net1.BytesSent != 0 && net2.BytesSent > net1.BytesSent {
itemNet.Up = float64(net2.BytesSent-net1.BytesSent) / 1024 / 60
}
if net2.BytesRecv != 0 && net1.BytesRecv != 0 {
if net2.BytesRecv != 0 && net1.BytesRecv != 0 && net2.BytesRecv > net1.BytesRecv {
itemNet.Down = float64(net2.BytesRecv-net1.BytesRecv) / 1024 / 60
}
netList = append(netList, itemNet)
@@ -145,21 +145,13 @@ func loadNetIO() {
if net2.Name == net1.Name {
var itemNet model.MonitorNetwork
itemNet.Name = net1.Name
if net2.BytesSent != 0 && net1.BytesSent != 0 {
if net2.BytesSent != 0 && net1.BytesSent != 0 && net2.BytesSent > net1.BytesSent {
itemNet.Up = float64(net2.BytesSent-net1.BytesSent) / 1024 / 60
}
if itemNet.Up > 10485760 {
itemNet.Up = 0
global.LOG.Errorf("net2: %v, net1: %v, BytesSent: %v \n", net2.BytesSent, net1.BytesSent, float64(net2.BytesSent-net1.BytesSent)/1024/60)
}
if net2.BytesRecv != 0 && net1.BytesRecv != 0 {
if net2.BytesRecv != 0 && net1.BytesRecv != 0 && net2.BytesRecv > net1.BytesRecv {
itemNet.Down = float64(net2.BytesRecv-net1.BytesRecv) / 1024 / 60
}
if itemNet.Down > 10485760 {
itemNet.Down = 0
global.LOG.Errorf("net2: %v, net1: %v, BytesRecv: %v \n", net2.BytesRecv, net1.BytesRecv, float64(net2.BytesRecv-net1.BytesRecv)/1024/60)
}
netList = append(netList, itemNet)
break
}
@@ -175,10 +167,12 @@ func StartMonitor(removeBefore bool, interval string) error {
if removeBefore {
global.Cron.Remove(cron.EntryID(global.MonitorCronID))
}
monitorID, err := global.Cron.AddJob(fmt.Sprintf("@every %sm", interval), NewIMonitorService())
imservice := NewIMonitorService()
monitorID, err := global.Cron.AddJob(fmt.Sprintf("@every %sm", interval), imservice)
if err != nil {
return err
}
imservice.Run()
global.MonitorCronID = int(monitorID)
return nil
}
+26 -19
View File
@@ -1,6 +1,7 @@
package service
import (
"fmt"
"io"
"net/http"
"os"
@@ -20,7 +21,7 @@ type NginxService struct {
}
type INginxService interface {
GetNginxConfig() (response.FileInfo, error)
GetNginxConfig() (*response.NginxFile, error)
GetConfigByScope(req request.NginxScopeReq) ([]response.NginxParam, error)
UpdateConfigByScope(req request.NginxConfigUpdate) error
GetStatus() (response.NginxStatus, error)
@@ -31,20 +32,17 @@ func NewINginxService() INginxService {
return &NginxService{}
}
func (n NginxService) GetNginxConfig() (response.FileInfo, error) {
func (n NginxService) GetNginxConfig() (*response.NginxFile, error) {
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return response.FileInfo{}, err
return nil, err
}
configPath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "conf", "nginx.conf")
info, err := files.NewFileInfo(files.FileOption{
Path: configPath,
Expand: true,
})
byteContent, err := files.NewFileOp().GetContent(configPath)
if err != nil {
return response.FileInfo{}, err
return nil, err
}
return response.FileInfo{FileInfo: *info}, nil
return &response.NginxFile{Content: string(byteContent)}, nil
}
func (n NginxService) GetConfigByScope(req request.NginxScopeReq) ([]response.NginxParam, error) {
@@ -64,7 +62,15 @@ func (n NginxService) UpdateConfigByScope(req request.NginxConfigUpdate) error {
}
func (n NginxService) GetStatus() (response.NginxStatus, error) {
res, err := http.Get("http://127.0.0.1/nginx_status")
httpPort, _, err := getAppInstallPort(constant.AppOpenresty)
if err != nil {
return response.NginxStatus{}, err
}
url := "http://127.0.0.1/nginx_status"
if httpPort != 80 {
url = fmt.Sprintf("http://127.0.0.1:%v/nginx_status", httpPort)
}
res, err := http.Get(url)
if err != nil {
return response.NginxStatus{}, err
}
@@ -86,31 +92,32 @@ func (n NginxService) GetStatus() (response.NginxStatus, error) {
func (n NginxService) UpdateConfigFile(req request.NginxConfigFileUpdate) error {
fileOp := files.NewFileOp()
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
filePath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "conf", "nginx.conf")
if err != nil {
return err
}
if req.Backup {
backupPath := path.Join(path.Dir(req.FilePath), "bak")
backupPath := path.Join(path.Dir(filePath), "bak")
if !fileOp.Stat(backupPath) {
if err := fileOp.CreateDir(backupPath, 0755); err != nil {
return err
}
}
newFile := path.Join(backupPath, "nginx.bak"+"-"+time.Now().Format("2006-01-02-15-04-05"))
if err := fileOp.Copy(req.FilePath, backupPath); err != nil {
if err := fileOp.Copy(filePath, backupPath); err != nil {
return err
}
if err := fileOp.Rename(path.Join(backupPath, "nginx.conf"), newFile); err != nil {
return err
}
}
oldContent, err := os.ReadFile(req.FilePath)
oldContent, err := os.ReadFile(filePath)
if err != nil {
return err
}
if err := fileOp.WriteFile(req.FilePath, strings.NewReader(req.Content), 0644); err != nil {
if err = fileOp.WriteFile(filePath, strings.NewReader(req.Content), 0644); err != nil {
return err
}
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return err
}
return nginxCheckAndReload(string(oldContent), req.FilePath, nginxInstall.ContainerName)
return nginxCheckAndReload(string(oldContent), filePath, nginxInstall.ContainerName)
}
+7 -9
View File
@@ -94,11 +94,7 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (err error) {
return
}
}
composeContent, envContent, forms, err := handleParams(create.Image, create.Type, newNameDir, create.Params)
if err != nil {
return
}
composeService, err := getComposeService(create.Name, newNameDir, composeContent, envContent, false)
composeContent, envContent, forms, err := handleParams(create.Image, create.Type, newNameDir, create.Source, create.Params)
if err != nil {
return
}
@@ -117,7 +113,7 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (err error) {
if err = runtimeRepo.Create(context.Background(), runtime); err != nil {
return
}
go buildRuntime(runtime, composeService, "")
go buildRuntime(runtime, "", false)
return
}
@@ -201,6 +197,9 @@ func (r *RuntimeService) Get(id uint) (*response.RuntimeRes, error) {
if err != nil {
return nil, err
}
if v, ok := envs["CONTAINER_PACKAGE_URL"]; ok {
res.Source = v
}
for _, form := range appForm.FormFields {
if v, ok := envs[form.EnvKey]; ok {
appParam := response.AppParam{
@@ -256,11 +255,10 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
return buserr.New(constant.ErrImageExist)
}
runtimeDir := path.Join(constant.RuntimeDir, runtime.Type, runtime.Name)
composeContent, envContent, _, err := handleParams(req.Image, runtime.Type, runtimeDir, req.Params)
composeContent, envContent, _, err := handleParams(req.Image, runtime.Type, runtimeDir, req.Source, req.Params)
if err != nil {
return err
}
composeService, err := getComposeService(runtime.Name, runtimeDir, composeContent, envContent, false)
if err != nil {
return err
}
@@ -277,6 +275,6 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
if err != nil {
return err
}
go buildRuntime(runtime, composeService, imageID)
go buildRuntime(runtime, imageID, req.Rebuild)
return nil
}
+53 -27
View File
@@ -1,27 +1,51 @@
package service
import (
"bytes"
"fmt"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"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/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/docker/cli/cli/command"
"github.com/subosito/gotenv"
"io"
"os"
"os/exec"
"path"
"strings"
)
func buildRuntime(runtime *model.Runtime, service *docker.ComposeService, oldImageID string) {
err := service.ComposeBuild()
func buildRuntime(runtime *model.Runtime, oldImageID string, rebuild bool) {
runtimePath := path.Join(constant.RuntimeDir, runtime.Type, runtime.Name)
composePath := path.Join(runtimePath, "docker-compose.yml")
logPath := path.Join(runtimePath, "build.log")
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
if err != nil {
fmt.Println("Failed to open log file:", err)
return
}
defer func() {
_ = logFile.Close()
}()
cmd := exec.Command("docker-compose", "-f", composePath, "build")
multiWriterStdout := io.MultiWriter(os.Stdout, logFile)
cmd.Stdout = multiWriterStdout
var stderrBuf bytes.Buffer
multiWriterStderr := io.MultiWriter(&stderrBuf, logFile, os.Stderr)
cmd.Stderr = multiWriterStderr
err = cmd.Run()
if err != nil {
runtime.Status = constant.RuntimeError
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + err.Error()
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + stderrBuf.String()
} else {
runtime.Status = constant.RuntimeNormal
runtime.Message = ""
if oldImageID != "" {
client, err := docker.NewClient()
if err == nil {
@@ -38,11 +62,34 @@ func buildRuntime(runtime *model.Runtime, service *docker.ComposeService, oldIma
global.LOG.Errorf("delete imageID [%s] error %v", oldImageID, err)
}
}
if rebuild && runtime.ID > 0 {
websites, _ := websiteRepo.GetBy(websiteRepo.WithRuntimeID(runtime.ID))
if len(websites) > 0 {
installService := NewIAppInstalledService()
installMap := make(map[uint]string)
for _, website := range websites {
if website.AppInstallID > 0 {
installMap[website.AppInstallID] = website.PrimaryDomain
}
}
for installID, domain := range installMap {
go func(installID uint, domain string) {
global.LOG.Infof("rebuild php runtime [%s] domain [%s]", runtime.Name, domain)
if err := installService.Operate(request.AppInstalledOperate{
InstallId: installID,
Operate: constant.Rebuild,
}); err != nil {
global.LOG.Errorf("rebuild php runtime [%s] domain [%s] error %v", runtime.Name, domain, err)
}
}(installID, domain)
}
}
}
}
_ = runtimeRepo.Save(runtime)
}
func handleParams(image, runtimeType, runtimeDir string, params map[string]interface{}) (composeContent []byte, envContent []byte, forms []byte, err error) {
func handleParams(image, runtimeType, runtimeDir, source string, params map[string]interface{}) (composeContent []byte, envContent []byte, forms []byte, err error) {
fileOp := files.NewFileOp()
composeContent, err = fileOp.GetContent(path.Join(runtimeDir, "docker-compose.yml"))
if err != nil {
@@ -67,6 +114,7 @@ func handleParams(image, runtimeType, runtimeDir string, params map[string]inter
params["PHP_EXTENSIONS"] = strings.Join(strArray, ",")
}
}
params["CONTAINER_PACKAGE_URL"] = source
}
newMap := make(map[string]string)
handleMap(params, newMap)
@@ -83,25 +131,3 @@ func handleParams(image, runtimeType, runtimeDir string, params map[string]inter
envContent = []byte(envStr)
return
}
func getComposeService(name, runtimeDir string, composeFile, env []byte, skipNormalization bool) (*docker.ComposeService, error) {
project, err := docker.GetComposeProject(name, runtimeDir, composeFile, env, skipNormalization)
if err != nil {
return nil, err
}
logPath := path.Join(runtimeDir, "build.log")
fileOp := files.NewFileOp()
if fileOp.Stat(logPath) {
_ = fileOp.DeleteFile(logPath)
}
file, err := os.Create(logPath)
if err != nil {
return nil, err
}
composeService, err := docker.NewComposeService(command.WithOutputStream(file))
if err != nil {
return nil, err
}
composeService.SetProject(project)
return composeService, nil
}
+23 -15
View File
@@ -7,6 +7,7 @@ import (
"encoding/pem"
"fmt"
"os"
"path"
"strconv"
"strings"
"time"
@@ -102,6 +103,12 @@ func (u *SettingService) Update(key, value string) error {
if err := ntp.UpdateSystemTimeZone(value); err != nil {
return err
}
case "AppStoreLastModified":
exist, _ := settingRepo.Get(settingRepo.WithByKey("AppStoreLastModified"))
if exist.ID == 0 {
_ = settingRepo.Create("AppStoreLastModified", value)
return nil
}
}
if err := settingRepo.Update(key, value); err != nil {
@@ -127,6 +134,7 @@ func (u *SettingService) Update(key, value string) error {
}
case "UserName", "Password":
_ = global.SESSION.Clean()
}
return nil
@@ -172,7 +180,7 @@ func (u *SettingService) UpdatePort(port uint) error {
}
func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
secretDir := global.CONF.System.BaseDir + "/1panel/secret/"
secretDir := path.Join(global.CONF.System.BaseDir, "1panel/secret")
if req.SSL == "disable" {
if err := settingRepo.Update("SSL", "disable"); err != nil {
return err
@@ -180,8 +188,8 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
if err := settingRepo.Update("SSLType", "self"); err != nil {
return err
}
_ = os.Remove(secretDir + "server.crt")
_ = os.Remove(secretDir + "server.key")
_ = os.Remove(path.Join(secretDir, "server.crt"))
_ = os.Remove(path.Join(secretDir, "server.key"))
go func() {
_, err := cmd.Exec("systemctl restart 1panel.service")
if err != nil {
@@ -220,7 +228,7 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
}
}
if req.SSLType == "import" {
cert, err := os.OpenFile(secretDir+"server.crt.tmp", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
cert, err := os.OpenFile(path.Join(secretDir, "server.crt.tmp"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
@@ -228,7 +236,7 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
if _, err := cert.WriteString(req.Cert); err != nil {
return err
}
key, err := os.OpenFile(secretDir+"server.key.tmp", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
key, err := os.OpenFile(path.Join(secretDir, "server.key.tmp"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
@@ -242,10 +250,10 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
}
fileOp := files.NewFileOp()
if err := fileOp.Rename(secretDir+"server.crt.tmp", secretDir+"server.crt"); err != nil {
if err := fileOp.Rename(path.Join(secretDir, "server.crt.tmp"), path.Join(secretDir, "server.crt")); err != nil {
return err
}
if err := fileOp.Rename(secretDir+"server.key.tmp", secretDir+"server.key"); err != nil {
if err := fileOp.Rename(path.Join(secretDir, "server.key.tmp"), path.Join(secretDir, "server.key")); err != nil {
return err
}
if err := settingRepo.Update("SSL", req.SSL); err != nil {
@@ -278,16 +286,16 @@ func (u *SettingService) LoadFromCert() (*dto.SSLInfo, error) {
}
switch sslType.Value {
case "import":
if _, err := os.Stat(global.CONF.System.BaseDir + "/1panel/secret/server.crt"); err != nil {
if _, err := os.Stat(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt")); err != nil {
return nil, fmt.Errorf("load server.crt file failed, err: %v", err)
}
certFile, _ := os.ReadFile(global.CONF.System.BaseDir + "/1panel/secret/server.crt")
certFile, _ := os.ReadFile(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt"))
data.Cert = string(certFile)
if _, err := os.Stat(global.CONF.System.BaseDir + "/1panel/secret/server.key"); err != nil {
if _, err := os.Stat(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.key")); err != nil {
return nil, fmt.Errorf("load server.key file failed, err: %v", err)
}
keyFile, _ := os.ReadFile(global.CONF.System.BaseDir + "/1panel/secret/server.key")
keyFile, _ := os.ReadFile(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.key"))
data.Key = string(keyFile)
case "select":
sslID, err := settingRepo.Get(settingRepo.WithByKey("SSLID"))
@@ -341,7 +349,7 @@ func (u *SettingService) UpdatePassword(c *gin.Context, old, new string) error {
func loadInfoFromCert() (*dto.SSLInfo, error) {
var info dto.SSLInfo
certFile := global.CONF.System.BaseDir + "/1panel/secret/server.crt"
certFile := path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt")
if _, err := os.Stat(certFile); err != nil {
return &info, err
}
@@ -369,16 +377,16 @@ func loadInfoFromCert() (*dto.SSLInfo, error) {
return &dto.SSLInfo{
Domain: strings.Join(domains, ","),
Timeout: certObj.NotAfter.Format("2006-01-02 15:04:05"),
RootPath: global.CONF.System.BaseDir + "/1panel/secret/server.crt",
RootPath: path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt"),
}, nil
}
func checkCertValid(domain string) error {
certificate, err := os.ReadFile(global.CONF.System.BaseDir + "/1panel/secret/server.crt.tmp")
certificate, err := os.ReadFile(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt.tmp"))
if err != nil {
return err
}
key, err := os.ReadFile(global.CONF.System.BaseDir + "/1panel/secret/server.key.tmp")
key, err := os.ReadFile(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.key.tmp"))
if err != nil {
return err
}
+404 -224
View File
@@ -5,7 +5,11 @@ import (
"encoding/json"
"fmt"
"os"
"path"
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto"
@@ -13,6 +17,7 @@ import (
"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/compose"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/jinzhu/copier"
@@ -31,8 +36,12 @@ type ISnapshotService interface {
SnapshotImport(req dto.SnapshotImport) error
Delete(req dto.BatchDeleteReq) error
LoadSnapShotStatus(id uint) (*dto.SnapshotStatus, error)
UpdateDescription(req dto.UpdateDescription) error
readFromJson(path string) (SnapshotJson, error)
HandleSnapshot(isCronjob bool, req dto.SnapshotCreate, timeNow string) (string, string, error)
}
func NewISnapshotService() ISnapshotService {
@@ -63,13 +72,14 @@ func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
}
}
for _, snap := range req.Names {
nameItems := strings.Split(snap, "_")
if !strings.HasPrefix(snap, "1panel_v") || !strings.HasSuffix(snap, ".tar.gz") || len(nameItems) != 3 {
return fmt.Errorf("incorrect snapshot name format of %s", snap)
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 {
return fmt.Errorf("incorrect snapshot name format of %s", snap)
return fmt.Errorf("incorrect snapshot name format of %s", shortName)
}
if strings.HasSuffix(snap, ".tar.gz") {
snap = strings.ReplaceAll(snap, ".tar.gz", "")
@@ -96,6 +106,18 @@ func (u *SnapshotService) UpdateDescription(req dto.UpdateDescription) error {
return snapshotRepo.Update(req.ID, map[string]interface{}{"description": req.Description})
}
func (u *SnapshotService) LoadSnapShotStatus(id uint) (*dto.SnapshotStatus, error) {
var data dto.SnapshotStatus
status, err := snapshotRepo.GetStatus(id)
if err != nil {
return nil, err
}
if err := copier.Copy(&data, &status); err != nil {
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
return &data, nil
}
type SnapshotJson struct {
OldBaseDir string `json:"oldBaseDir"`
OldDockerDataDir string `json:"oldDockerDataDir"`
@@ -110,116 +132,9 @@ type SnapshotJson struct {
}
func (u *SnapshotService) SnapshotCreate(req dto.SnapshotCreate) error {
global.LOG.Info("start to create snapshot now")
localDir, err := loadLocalDir()
if err != nil {
if _, _, err := u.HandleSnapshot(false, req, time.Now().Format("20060102150405")); err != nil {
return err
}
backup, err := backupRepo.Get(commonRepo.WithByType(req.From))
if err != nil {
return err
}
backupAccount, err := NewIBackupService().NewClient(&backup)
if err != nil {
return err
}
timeNow := time.Now().Format("20060102150405")
versionItem, _ := settingRepo.Get(settingRepo.WithByKey("SystemVersion"))
rootDir := fmt.Sprintf("%s/system/1panel_%s_%s", localDir, versionItem.Value, timeNow)
backupPanelDir := fmt.Sprintf("%s/1panel", rootDir)
_ = os.MkdirAll(backupPanelDir, os.ModePerm)
backupDockerDir := fmt.Sprintf("%s/docker", rootDir)
_ = os.MkdirAll(backupDockerDir, os.ModePerm)
_ = settingRepo.Update("SystemStatus", "Snapshoting")
snap := model.Snapshot{
Name: fmt.Sprintf("1panel_%s_%s", versionItem.Value, timeNow),
Description: req.Description,
From: req.From,
Version: versionItem.Value,
Status: constant.StatusSuccess,
}
_ = snapshotRepo.Create(&snap)
go func() {
defer func() {
_ = os.RemoveAll(rootDir)
}()
fileOp := files.NewFileOp()
dockerDataDir, liveRestoreStatus, err := u.loadDockerDataDir()
if err != nil {
updateSnapshotStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
_, _ = cmd.Exec("systemctl stop docker")
if err := u.handleDockerDatas(fileOp, "snapshot", dockerDataDir, backupDockerDir); err != nil {
updateSnapshotStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
if err := u.handleDaemonJson(fileOp, "snapshot", "", backupDockerDir); err != nil {
updateSnapshotStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
if err := u.handlePanelBinary(fileOp, "snapshot", "", backupPanelDir+"/1panel"); err != nil {
updateSnapshotStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
if err := u.handlePanelctlBinary(fileOp, "snapshot", "", backupPanelDir+"/1pctl"); err != nil {
updateSnapshotStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
if err := u.handlePanelService(fileOp, "snapshot", "", backupPanelDir+"/1panel.service"); err != nil {
updateSnapshotStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
if err := u.handleBackupDatas(fileOp, "snapshot", localDir, backupPanelDir); err != nil {
updateSnapshotStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
if err := u.handlePanelDatas(snap.ID, fileOp, "snapshot", global.CONF.System.BaseDir+"/1panel", backupPanelDir, localDir, dockerDataDir); err != nil {
updateSnapshotStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
_, _ = cmd.Exec("systemctl restart docker")
snapJson := SnapshotJson{
BaseDir: global.CONF.System.BaseDir,
DockerDataDir: dockerDataDir,
BackupDataDir: localDir,
PanelDataDir: global.CONF.System.BaseDir + "/1panel",
LiveRestoreEnabled: liveRestoreStatus,
}
if err := u.saveJson(snapJson, rootDir); err != nil {
updateSnapshotStatus(snap.ID, constant.StatusFailed, fmt.Sprintf("save snapshot json failed, err: %v", err))
return
}
if err := handleTar(rootDir, fmt.Sprintf("%s/system", localDir), fmt.Sprintf("1panel_%s_%s.tar.gz", versionItem.Value, timeNow), ""); err != nil {
updateSnapshotStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
_ = settingRepo.Update("SystemStatus", "Free")
global.LOG.Infof("start to upload snapshot to %s, please wait", backup.Type)
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusUploading})
localPath := fmt.Sprintf("%s/system/1panel_%s_%s.tar.gz", localDir, versionItem.Value, timeNow)
itemBackupPath := strings.TrimLeft(backup.BackupPath, "/")
itemBackupPath = strings.TrimRight(itemBackupPath, "/")
if ok, err := backupAccount.Upload(localPath, fmt.Sprintf("%s/system_snapshot/1panel_%s_%s.tar.gz", itemBackupPath, versionItem.Value, timeNow)); err != nil || !ok {
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
global.LOG.Errorf("upload snapshot to %s failed, err: %v", backup.Type, err)
return
}
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusSuccess})
_ = os.RemoveAll(fmt.Sprintf("%s/system/1panel_%s_%s.tar.gz", localDir, versionItem.Value, timeNow))
global.LOG.Infof("upload snapshot to %s success", backup.Type)
}()
return nil
}
@@ -232,6 +147,7 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
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
@@ -248,7 +164,7 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
if err != nil {
return err
}
baseDir := fmt.Sprintf("%s/system/%s", localDir, snap.Name)
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)
}
@@ -256,13 +172,17 @@ 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.TrimLeft(backup.BackupPath, "/")
itemBackupPath = strings.TrimRight(itemBackupPath, "/")
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 {
@@ -296,39 +216,51 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
if snap.InterruptStep == "Readjson" {
isReTry = false
}
u.OriginalPath = fmt.Sprintf("%s/original_%s", snapJson.BaseDir, snap.Name)
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 = global.CONF.System.BaseDir + "/1panel"
snapJson.OldPanelDataDir = path.Join(global.CONF.System.BaseDir, "1panel")
snapJson.OldBackupDataDir = localDir
recoverPanelDir := fmt.Sprintf("%s/%s/1panel", baseDir, snap.Name)
liveRestore := false
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
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
}
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())
@@ -375,6 +307,11 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
}
isReTry = false
}
if isNewSnapshot {
_ = rebuildAllAppInstall()
}
_ = os.RemoveAll(rootDir)
global.LOG.Info("recover successful")
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
@@ -396,30 +333,59 @@ func (u *SnapshotService) SnapshotRollback(req dto.SnapshotRecover) error {
return err
}
fileOp := files.NewFileOp()
isNewSnapshot := isNewSnapVersion(snap.Version)
rootDir := fmt.Sprintf("%s/system/%s/%s", localDir, snap.Name, snap.Name)
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/original_%s", snapJson.OldBaseDir, snap.Name)
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
}
_, _ = 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
}
if snap.InterruptStep == "DockerDir" {
_, _ = cmd.Exec("systemctl restart docker")
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 {
@@ -427,17 +393,6 @@ func (u *SnapshotService) SnapshotRollback(req dto.SnapshotRecover) error {
return
}
if snap.InterruptStep == "DaemonJson" {
_, _ = cmd.Exec("systemctl restart docker")
return
}
if snapJson.LiveRestoreEnabled {
if err := u.updateLiveRestore(true); err != nil {
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
}
if snap.InterruptStep == "UpdateLiveRestore" {
_, _ = cmd.Exec("systemctl restart dockere")
return
}
@@ -514,14 +469,135 @@ func (u *SnapshotService) readFromJson(path string) (SnapshotJson, error) {
return snap, nil
}
func (u *SnapshotService) HandleSnapshot(isCronjob bool, req dto.SnapshotCreate, timeNow string) (string, string, error) {
localDir, err := loadLocalDir()
if err != nil {
return "", "", err
}
var (
rootDir string
snap model.Snapshot
snapStatus model.SnapshotStatus
)
if req.ID == 0 {
versionItem, _ := settingRepo.Get(settingRepo.WithByKey("SystemVersion"))
name := fmt.Sprintf("1panel_%s_%s", versionItem.Value, timeNow)
if isCronjob {
name = fmt.Sprintf("snapshot_1panel_%s_%s", versionItem.Value, timeNow)
}
rootDir = path.Join(localDir, "system", name)
snap = model.Snapshot{
Name: name,
Description: req.Description,
From: req.From,
Version: versionItem.Value,
Status: constant.StatusWaiting,
}
_ = snapshotRepo.Create(&snap)
snapStatus.SnapID = snap.ID
_ = snapshotRepo.CreateStatus(&snapStatus)
} else {
snap, err = snapshotRepo.Get(commonRepo.WithByID(req.ID))
if err != nil {
return "", "", err
}
snapStatus, _ = snapshotRepo.GetStatus(snap.ID)
if snapStatus.ID == 0 {
snapStatus.SnapID = snap.ID
_ = snapshotRepo.CreateStatus(&snapStatus)
}
rootDir = path.Join(localDir, fmt.Sprintf("system/%s", snap.Name))
}
var wg sync.WaitGroup
itemHelper := snapHelper{SnapID: snap.ID, Status: &snapStatus, Wg: &wg, FileOp: files.NewFileOp(), Ctx: context.Background()}
backupPanelDir := path.Join(rootDir, "1panel")
_ = os.MkdirAll(backupPanelDir, os.ModePerm)
backupDockerDir := path.Join(rootDir, "docker")
_ = os.MkdirAll(backupDockerDir, os.ModePerm)
jsonItem := SnapshotJson{
BaseDir: global.CONF.System.BaseDir,
BackupDataDir: localDir,
PanelDataDir: path.Join(global.CONF.System.BaseDir, "1panel"),
}
if snapStatus.PanelInfo != constant.StatusDone {
wg.Add(1)
go snapJson(itemHelper, jsonItem, rootDir)
}
if snapStatus.Panel != constant.StatusDone {
wg.Add(1)
go snapPanel(itemHelper, backupPanelDir)
}
if snapStatus.DaemonJson != constant.StatusDone {
wg.Add(1)
go snapDaemonJson(itemHelper, backupDockerDir)
}
if snapStatus.AppData != constant.StatusDone {
wg.Add(1)
go snapAppData(itemHelper, backupDockerDir)
}
if snapStatus.BackupData != constant.StatusDone {
wg.Add(1)
go snapBackup(itemHelper, localDir, backupPanelDir)
}
if snapStatus.PanelData != constant.StatusDone {
wg.Add(1)
go snapPanelData(itemHelper, localDir, backupPanelDir)
}
if !isCronjob {
go func() {
wg.Wait()
if !checkIsAllDone(snap.ID) {
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
return
}
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 {
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
return
}
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusSuccess})
}()
return "", "", nil
}
wg.Wait()
if !checkIsAllDone(snap.ID) {
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
return loadLogByStatus(snapStatus), snap.Name, fmt.Errorf("snapshot %s backup failed", snap.Name)
}
snapCompress(itemHelper, rootDir)
if snapStatus.Compress != constant.StatusDone {
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
return loadLogByStatus(snapStatus), snap.Name, fmt.Errorf("snapshot %s compress failed", snap.Name)
}
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 loadLogByStatus(snapStatus), snap.Name, fmt.Errorf("snapshot %s upload failed", snap.Name)
}
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusSuccess})
return loadLogByStatus(snapStatus), snap.Name, nil
}
func (u *SnapshotService) handleDockerDatas(fileOp files.FileOp, operation string, source, target string) error {
switch operation {
case "snapshot":
if err := u.handleTar(source, target, "docker_data.tar.gz", ""); err != nil {
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 := u.handleTar(target, u.OriginalPath, "docker_data.tar.gz", ""); err != nil {
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 {
@@ -540,6 +616,31 @@ func (u *SnapshotService) handleDockerDatas(fileOp files.FileOp, operation strin
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" {
@@ -574,7 +675,7 @@ func (u *SnapshotService) handlePanelBinary(fileOp files.FileOp, operation strin
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(panelPath, target); err != nil {
if err := cpBinary([]string{panelPath}, target); err != nil {
return fmt.Errorf("backup 1panel binary failed, err: %v", err)
}
}
@@ -583,7 +684,7 @@ func (u *SnapshotService) handlePanelBinary(fileOp files.FileOp, operation strin
if _, err := os.Stat(source); err != nil {
return fmt.Errorf("1panel binary is not found in snapshot, err: %v", err)
} else {
if err := cpBinary(source, "/usr/local/bin/1panel"); err != nil {
if err := cpBinary([]string{source}, "/usr/local/bin/1panel"); err != nil {
return fmt.Errorf("recover 1panel binary failed, err: %v", err)
}
}
@@ -591,13 +692,14 @@ func (u *SnapshotService) handlePanelBinary(fileOp files.FileOp, operation strin
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(panelctlPath, target); err != nil {
if err := cpBinary([]string{panelctlPath}, target); err != nil {
return fmt.Errorf("backup 1pctl binary failed, err: %v", err)
}
}
@@ -606,7 +708,7 @@ func (u *SnapshotService) handlePanelctlBinary(fileOp files.FileOp, operation st
if _, err := os.Stat(source); err != nil {
return fmt.Errorf("1pctl binary is not found in snapshot, err: %v", err)
} else {
if err := cpBinary(source, "/usr/local/bin/1pctl"); err != nil {
if err := cpBinary([]string{source}, "/usr/local/bin/1pctl"); err != nil {
return fmt.Errorf("recover 1pctl binary failed, err: %v", err)
}
}
@@ -621,7 +723,7 @@ func (u *SnapshotService) handlePanelService(fileOp files.FileOp, operation stri
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(panelServicePath, target); err != nil {
if err := cpBinary([]string{panelServicePath}, target); err != nil {
return fmt.Errorf("backup 1panel service failed, err: %v", err)
}
}
@@ -630,7 +732,7 @@ func (u *SnapshotService) handlePanelService(fileOp files.FileOp, operation stri
if _, err := os.Stat(source); err != nil {
return fmt.Errorf("1panel service is not found in snapshot, err: %v", err)
} else {
if err := cpBinary(source, "/etc/systemd/system/1panel.service"); err != nil {
if err := cpBinary([]string{source}, "/etc/systemd/system/1panel.service"); err != nil {
return fmt.Errorf("recover 1panel service failed, err: %v", err)
}
}
@@ -641,12 +743,8 @@ func (u *SnapshotService) handlePanelService(fileOp files.FileOp, operation stri
func (u *SnapshotService) handleBackupDatas(fileOp files.FileOp, operation string, source, target string) error {
switch operation {
case "snapshot":
if err := u.handleTar(source, target, "1panel_backup.tar.gz", "./system;"); err != nil {
return fmt.Errorf("backup panel local backup dir data failed, err: %v", err)
}
case "recover":
if err := u.handleTar(target, u.OriginalPath, "1panel_backup.tar.gz", "./system;"); err != nil {
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 {
@@ -668,7 +766,7 @@ func (u *SnapshotService) handleBackupDatas(fileOp files.FileOp, operation strin
func (u *SnapshotService) handlePanelDatas(snapID uint, fileOp files.FileOp, operation string, source, target, backupDir, dockerDir string) error {
switch operation {
case "snapshot":
exclusionRules := "./tmp;./cache;./db/1Panel.db-*;"
exclusionRules := "./tmp;./log;./cache;"
if strings.Contains(backupDir, source) {
exclusionRules += ("." + strings.ReplaceAll(backupDir, source, "") + ";")
}
@@ -676,11 +774,11 @@ func (u *SnapshotService) handlePanelDatas(snapID uint, fileOp files.FileOp, ope
exclusionRules += ("." + strings.ReplaceAll(dockerDir, source, "") + ";")
}
if err := u.handleTar(source, target, "1panel_data.tar.gz", exclusionRules); err != nil {
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/;./cache;"
exclusionRules := "./tmp;./log;./cache;"
if strings.Contains(backupDir, target) {
exclusionRules += ("." + strings.ReplaceAll(backupDir, target, "") + ";")
}
@@ -689,19 +787,22 @@ func (u *SnapshotService) handlePanelDatas(snapID uint, fileOp files.FileOp, ope
}
_ = snapshotRepo.Update(snapID, map[string]interface{}{"recover_status": ""})
if err := u.handleTar(target, u.OriginalPath, "1panel_data.tar.gz", exclusionRules); err != nil {
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"))
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"))
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"))
if err := u.handleUnTar(source+"/1panel_data.tar.gz", target); err != nil {
return fmt.Errorf("rollback panel data failed, err: %v", err)
}
@@ -728,9 +829,13 @@ func (u *SnapshotService) Delete(req dto.BatchDeleteReq) error {
return err
}
for _, snap := range backups {
if _, err := os.Stat(fmt.Sprintf("%s/system/%s/%s.tar.gz", localDir, snap.Name, snap.Name)); err == nil {
_ = os.Remove(fmt.Sprintf("%s/system/%s/%s.tar.gz", localDir, snap.Name, snap.Name))
}
itemFile := path.Join(localDir, "system", snap.Name)
_ = os.RemoveAll(itemFile)
itemTarFile := path.Join(global.CONF.System.TmpDir, "system", snap.Name+".tar.gz")
_ = os.Remove(itemTarFile)
_ = snapshotRepo.DeleteStatus(snap.ID)
}
if err := snapshotRepo.Delete(commonRepo.WithIdsIn(req.Ids)); err != nil {
return err
@@ -739,18 +844,6 @@ func (u *SnapshotService) Delete(req dto.BatchDeleteReq) error {
return nil
}
func updateSnapshotStatus(id uint, status string, message string) {
if status != constant.StatusSuccess {
global.LOG.Errorf("snapshot failed, err: %s", message)
}
if err := snapshotRepo.Update(id, map[string]interface{}{
"status": status,
"message": message,
}); err != nil {
global.LOG.Errorf("update snap snapshot status failed, err: %v", err)
}
_ = settingRepo.Update("SystemStatus", "Free")
}
func updateRecoverStatus(id uint, interruptStep, status string, message string) {
if status != constant.StatusSuccess {
global.LOG.Errorf("recover failed, err: %s", message)
@@ -790,10 +883,11 @@ func updateRollbackStatus(id uint, status string, message string) {
}
}
func cpBinary(src, dst string) error {
stderr, err := cmd.Exec(fmt.Sprintf("\\cp -f %s %s", src, dst))
func cpBinary(src []string, dst string) error {
global.LOG.Debugf(fmt.Sprintf("\\cp -f %s %s", strings.Join(src, " "), dst))
stdout, err := cmd.Exec(fmt.Sprintf("\\cp -f %s %s", strings.Join(src, " "), dst))
if err != nil {
return errors.New(stderr)
return fmt.Errorf("cp file failed, stdout: %v, err: %v", stdout, err)
}
return nil
}
@@ -845,33 +939,6 @@ func (u *SnapshotService) updateLiveRestore(enabled bool) error {
return nil
}
func (u *SnapshotService) handleTar(sourceDir, targetDir, name, exclusionRules string) error {
if _, err := os.Stat(targetDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(targetDir, os.ModePerm); err != nil {
return err
}
}
exStr := ""
excludes := strings.Split(exclusionRules, ";")
for _, exclude := range excludes {
if len(exclude) == 0 {
continue
}
exStr += " --exclude "
exStr += exclude
}
commands := fmt.Sprintf("tar --warning=no-file-changed -zcf %s %s -C %s .", targetDir+"/"+name, exStr, sourceDir)
global.LOG.Debug(commands)
stdout, err := cmd.ExecWithTimeOut(commands, 30*time.Minute)
if err != nil {
global.LOG.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err)
return errors.New(stdout)
}
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 {
@@ -883,8 +950,121 @@ func (u *SnapshotService) handleUnTar(sourceDir, targetDir string) error {
global.LOG.Debug(commands)
stdout, err := cmd.ExecWithTimeOut(commands, 30*time.Minute)
if err != nil {
global.LOG.Errorf("do handle untar failed, stdout: %s, err: %v", stdout, err)
return errors.New(stdout)
if len(stdout) != 0 {
global.LOG.Errorf("do handle untar failed, stdout: %s, err: %v", stdout, err)
return fmt.Errorf("do handle untar failed, stdout: %s, err: %v", stdout, err)
}
}
return nil
}
func rebuildAllAppInstall() error {
global.LOG.Debug("start to rebuild all app")
appInstalls, err := appInstallRepo.ListBy()
if err != nil {
global.LOG.Errorf("get all app installed for rebuild failed, err: %v", err)
return err
}
var wg sync.WaitGroup
for i := 0; i < len(appInstalls); i++ {
wg.Add(1)
appInstalls[i].Status = constant.Rebuilding
_ = appInstallRepo.Save(context.Background(), &appInstalls[i])
go func(app model.AppInstall) {
defer wg.Done()
dockerComposePath := app.GetComposePath()
out, err := compose.Down(dockerComposePath)
if err != nil {
_ = handleErr(app, err, out)
return
}
out, err = compose.Up(dockerComposePath)
if err != nil {
_ = handleErr(app, err, out)
return
}
app.Status = constant.Running
_ = appInstallRepo.Save(context.Background(), &app)
}(appInstalls[i])
}
wg.Wait()
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
}
if status.Panel != constant.StatusDone {
return false
}
if status.PanelInfo != constant.StatusDone {
return false
}
if status.DaemonJson != constant.StatusDone {
return false
}
if status.AppData != constant.StatusDone {
return false
}
if status.PanelData != constant.StatusDone {
return false
}
if status.BackupData != constant.StatusDone {
return false
}
return true
}
func loadLogByStatus(status model.SnapshotStatus) string {
logs := ""
logs += fmt.Sprintf("Write 1Panel basic information: %s \n", status.PanelInfo)
logs += fmt.Sprintf("Backup 1Panel system files: %s \n", status.Panel)
logs += fmt.Sprintf("Backup Docker configuration file: %s \n", status.DaemonJson)
logs += fmt.Sprintf("Backup installed apps from 1Panel: %s \n", status.AppData)
logs += fmt.Sprintf("Backup 1Panel data directory: %s \n", status.PanelData)
logs += fmt.Sprintf("Backup local backup directory for 1Panel: %s \n", status.BackupData)
logs += fmt.Sprintf("Create snapshot file: %s \n", status.Compress)
logs += fmt.Sprintf("Snapshot size: %s \n", status.Size)
logs += fmt.Sprintf("Upload snapshot file: %s \n", status.Upload)
return logs
}
+236
View File
@@ -0,0 +1,236 @@
package service
import (
"context"
"encoding/json"
"fmt"
"os"
"path"
"regexp"
"strings"
"sync"
"time"
"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/common"
"github.com/1Panel-dev/1Panel/backend/utils/files"
)
type snapHelper struct {
SnapID uint
Status *model.SnapshotStatus
Ctx context.Context
FileOp files.FileOp
Wg *sync.WaitGroup
}
func snapJson(snap snapHelper, snapJson SnapshotJson, targetDir string) {
defer snap.Wg.Done()
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel_info": constant.Running})
status := constant.StatusDone
remarkInfo, _ := json.MarshalIndent(snapJson, "", "\t")
if err := os.WriteFile(fmt.Sprintf("%s/snapshot.json", targetDir), remarkInfo, 0640); err != nil {
status = err.Error()
}
snap.Status.PanelInfo = status
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel_info": status})
}
func snapPanel(snap snapHelper, targetDir string) {
defer snap.Wg.Done()
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel": constant.Running})
status := constant.StatusDone
if err := cpBinary([]string{"/usr/local/bin/1panel", "/usr/local/bin/1pctl", "/etc/systemd/system/1panel.service"}, targetDir); err != nil {
status = err.Error()
}
snap.Status.Panel = status
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel": status})
}
func snapDaemonJson(snap snapHelper, targetDir string) {
defer snap.Wg.Done()
status := constant.StatusDone
if !snap.FileOp.Stat("/etc/docker/daemon.json") {
snap.Status.DaemonJson = status
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"daemon_json": status})
return
}
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"daemon_json": constant.Running})
if err := cpBinary([]string{"/etc/docker/daemon.json"}, path.Join(targetDir, "daemon.json")); err != nil {
status = err.Error()
}
snap.Status.DaemonJson = status
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"daemon_json": status})
}
func snapAppData(snap snapHelper, targetDir string) {
defer snap.Wg.Done()
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"app_data": constant.Running})
appInstalls, err := appInstallRepo.ListBy()
if err != nil {
snap.Status.AppData = err.Error()
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"app_data": err.Error()})
return
}
runtimes, err := runtimeRepo.List()
if err != nil {
snap.Status.AppData = err.Error()
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"app_data": err.Error()})
return
}
imageRegex := regexp.MustCompile(`image:\s*(.*)`)
var imageSaveList []string
existStr, _ := cmd.Exec("docker images | awk '{print $1\":\"$2}' | grep -v REPOSITORY:TAG")
existImages := strings.Split(existStr, "\n")
duplicateMap := make(map[string]bool)
for _, app := range appInstalls {
matches := imageRegex.FindAllStringSubmatch(app.DockerCompose, -1)
for _, match := range matches {
for _, existImage := range existImages {
if match[1] == existImage && !duplicateMap[match[1]] {
imageSaveList = append(imageSaveList, match[1])
duplicateMap[match[1]] = true
}
}
}
}
for _, rumtime := range runtimes {
for _, existImage := range existImages {
if rumtime.Image == existImage && !duplicateMap[rumtime.Image] {
imageSaveList = append(imageSaveList, rumtime.Image)
duplicateMap[rumtime.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
}
snap.Status.AppData = constant.StatusDone
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"app_data": constant.StatusDone})
}
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 {
status = err.Error()
}
snap.Status.BackupData = status
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"backup_data": status})
}
func snapPanelData(snap snapHelper, localDir, targetDir string) {
defer snap.Wg.Done()
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel_data": constant.Running})
status := constant.StatusDone
dataDir := path.Join(global.CONF.System.BaseDir, "1panel")
exclusionRules := "./tmp;./log;./cache;"
if strings.Contains(localDir, dataDir) {
exclusionRules += ("." + strings.ReplaceAll(localDir, dataDir, "") + ";")
}
_ = snapshotRepo.Update(snap.SnapID, map[string]interface{}{"status": "OnSaveData"})
if err := handleSnapTar(dataDir, targetDir, "1panel_data.tar.gz", exclusionRules); err != nil {
status = err.Error()
}
_ = snapshotRepo.Update(snap.SnapID, map[string]interface{}{"status": constant.StatusWaiting})
snap.Status.PanelData = status
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel_data": status})
}
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))
if err := snap.FileOp.Compress([]string{rootDir}, tmpDir, fileName, files.TarGz); err != nil {
snap.Status.Compress = err.Error()
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"compress": err.Error()})
return
}
stat, err := os.Stat(path.Join(tmpDir, fileName))
if err != nil {
snap.Status.Compress = err.Error()
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"compress": err.Error()})
return
}
size := common.LoadSizeUnit2F(float64(stat.Size()))
global.LOG.Debugf("compress successful! size of file: %s", size)
snap.Status.Compress = constant.StatusDone
snap.Status.Size = size
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"compress": constant.StatusDone, "size": size})
}
func snapUpload(snap snapHelper, account 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})
backup, err := backupRepo.Get(commonRepo.WithByType(account))
if err != nil {
snap.Status.Upload = err.Error()
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"upload": err.Error()})
return
}
client, err := NewIBackupService().NewClient(&backup)
if err != nil {
snap.Status.Upload = err.Error()
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"upload": err.Error()})
return
}
target := path.Join(strings.TrimPrefix(backup.BackupPath, "/"), "system_snapshot", path.Base(file))
global.LOG.Debugf("start upload snapshot to %s, dir: %s", backup.Type, target)
if _, err := client.Upload(source, target); err != nil {
snap.Status.Upload = err.Error()
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"upload": err.Error()})
return
}
snap.Status.Upload = constant.StatusDone
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"upload": constant.StatusDone})
}
func handleSnapTar(sourceDir, targetDir, name, exclusionRules string) error {
if _, err := os.Stat(targetDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(targetDir, os.ModePerm); err != nil {
return err
}
}
exStr := ""
excludes := strings.Split(exclusionRules, ";")
for _, exclude := range excludes {
if len(exclude) == 0 {
continue
}
exStr += " --exclude "
exStr += exclude
}
commands := fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read -zcf %s %s -C %s .", targetDir+"/"+name, exStr, sourceDir)
global.LOG.Debug(commands)
stdout, err := cmd.ExecWithTimeOut(commands, 30*time.Minute)
if err != nil {
if len(stdout) != 0 {
global.LOG.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err)
return fmt.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err)
}
}
return nil
}
+198 -162
View File
@@ -11,12 +11,15 @@ import (
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"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/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/1Panel-dev/1Panel/backend/utils/qqwry"
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
"github.com/pkg/errors"
)
const sshPath = "/etc/ssh/sshd_config"
@@ -31,6 +34,8 @@ type ISSHService interface {
GenerateSSH(req dto.GenerateSSH) error
LoadSSHSecret(mode string) (string, error)
LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error)
LoadSSHConf() (string, error)
}
func NewISSHService() ISSHService {
@@ -48,19 +53,20 @@ func (u *SSHService) GetSSHInfo() (*dto.SSHInfo, error) {
PermitRootLogin: "yes",
UseDNS: "yes",
}
sudo := cmd.SudoHandleCmd()
stdout, err := cmd.Execf("%s systemctl status sshd", sudo)
serviceName, err := loadServiceName()
if err != nil {
data.Message = stdout
data.Status = constant.StatusDisable
}
stdLines := strings.Split(stdout, "\n")
for _, stdline := range stdLines {
if strings.Contains(stdline, "active (running)") {
data.Message = err.Error()
} else {
active, err := systemctl.IsActive(serviceName)
if !active {
data.Status = constant.StatusDisable
data.Message = err.Error()
} else {
data.Status = constant.StatusEnable
break
}
}
sshConf, err := os.ReadFile(sshPath)
if err != nil {
data.Message = err.Error()
@@ -92,10 +98,14 @@ func (u *SSHService) GetSSHInfo() (*dto.SSHInfo, error) {
func (u *SSHService) OperateSSH(operation string) error {
if operation == "start" || operation == "stop" || operation == "restart" {
sudo := cmd.SudoHandleCmd()
stdout, err := cmd.Execf("%s systemctl %s sshd", sudo, operation)
serviceName, err := loadServiceName()
if err != nil {
return fmt.Errorf("%s sshd failed, stdout: %s, err: %v", operation, stdout, err)
return err
}
sudo := cmd.SudoHandleCmd()
stdout, err := cmd.Execf("%s systemctl %s %s", sudo, operation, serviceName)
if err != nil {
return fmt.Errorf("%s %s failed, stdout: %s, err: %v", operation, serviceName, stdout, err)
}
return nil
}
@@ -103,6 +113,11 @@ func (u *SSHService) OperateSSH(operation string) error {
}
func (u *SSHService) Update(key, value string) error {
serviceName, err := loadServiceName()
if err != nil {
return err
}
sshConf, err := os.ReadFile(sshPath)
if err != nil {
return err
@@ -127,11 +142,17 @@ func (u *SSHService) Update(key, value string) error {
_, _ = cmd.Execf("%s semanage port -a -t ssh_port_t -p tcp %s", sudo, value)
}
}
_, _ = cmd.Execf("%s systemctl restart sshd", sudo)
_, _ = cmd.Execf("%s systemctl restart %s", sudo, serviceName)
return nil
}
func (u *SSHService) UpdateByFile(value string) error {
serviceName, err := loadServiceName()
if err != nil {
return err
}
file, err := os.OpenFile(sshPath, os.O_WRONLY|os.O_TRUNC, 0666)
if err != nil {
return err
@@ -141,11 +162,14 @@ func (u *SSHService) UpdateByFile(value string) error {
return err
}
sudo := cmd.SudoHandleCmd()
_, _ = cmd.Execf("%s systemctl restart sshd", sudo)
_, _ = cmd.Execf("%s systemctl restart %s", sudo, serviceName)
return nil
}
func (u *SSHService) GenerateSSH(req dto.GenerateSSH) error {
if cmd.CheckIllegal(req.EncryptionMode, req.Password) {
return buserr.New(constant.ErrCmdIllegal)
}
currentUser, err := user.Current()
if err != nil {
return fmt.Errorf("load current user failed, err: %v", err)
@@ -202,8 +226,13 @@ func (u *SSHService) LoadSSHSecret(mode string) (string, error) {
return string(file), err
}
type sshFileItem struct {
Name string
Year int
}
func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
var fileList []string
var fileList []sshFileItem
var data dto.SSHLog
baseDir := "/var/log"
if err := filepath.Walk(baseDir, func(pathItem string, info os.FileInfo, err error) error {
@@ -211,12 +240,15 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
return err
}
if !info.IsDir() && strings.HasPrefix(info.Name(), "secure") || strings.HasPrefix(info.Name(), "auth") {
if strings.HasSuffix(info.Name(), ".gz") {
if !strings.HasSuffix(info.Name(), ".gz") {
fileList = append(fileList, sshFileItem{Name: pathItem, Year: info.ModTime().Year()})
return nil
}
itemFileName := strings.TrimSuffix(pathItem, ".gz")
if _, err := os.Stat(itemFileName); err != nil && os.IsNotExist(err) {
if err := handleGunzip(pathItem); err == nil {
fileList = append(fileList, strings.ReplaceAll(pathItem, ".gz", ""))
fileList = append(fileList, sshFileItem{Name: itemFileName, Year: info.ModTime().Year()})
}
} else {
fileList = append(fileList, pathItem)
}
}
return nil
@@ -230,80 +262,73 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
command = fmt.Sprintf(" | grep '%s'", req.Info)
}
for i := 0; i < len(fileList); i++ {
withAppend := len(data.Logs) < req.Page*req.PageSize
if req.Status != constant.StatusSuccess {
if strings.HasPrefix(path.Base(fileList[i]), "secure") {
commandItem := fmt.Sprintf("cat %s | grep -a 'Failed password for' | grep -v 'invalid' %s", fileList[i], command)
dataItem, itemTotal := loadFailedSecureDatas(commandItem, withAppend)
data.FailedCount += itemTotal
data.TotalCount += itemTotal
data.Logs = append(data.Logs, dataItem...)
}
if strings.HasPrefix(path.Base(fileList[i]), "auth.log") {
commandItem := fmt.Sprintf("cat %s | grep -a 'Connection closed by authenticating user' | grep -a 'preauth' %s", fileList[i], command)
dataItem, itemTotal := loadFailedAuthDatas(commandItem, withAppend)
data.FailedCount += itemTotal
data.TotalCount += itemTotal
data.Logs = append(data.Logs, dataItem...)
}
}
if req.Status != constant.StatusFailed {
commandItem := fmt.Sprintf("cat %s | grep -a Accepted %s", fileList[i], command)
dataItem, itemTotal := loadSuccessDatas(commandItem, withAppend)
data.TotalCount += itemTotal
data.Logs = append(data.Logs, dataItem...)
}
}
data.SuccessfulCount = data.TotalCount - data.FailedCount
if len(data.Logs) < 1 {
return nil, nil
}
var itemDatas []dto.SSHHistory
total, start, end := len(data.Logs), (req.Page-1)*req.PageSize, req.Page*req.PageSize
if start > total {
itemDatas = make([]dto.SSHHistory, 0)
} else {
if end >= total {
end = total
}
itemDatas = data.Logs[start:end]
}
data.Logs = itemDatas
timeNow := time.Now()
showCountFrom := (req.Page - 1) * req.PageSize
showCountTo := req.Page * req.PageSize
nyc, _ := time.LoadLocation(common.LoadTimeZone())
qqWry, err := qqwry.NewQQwry()
if err != nil {
global.LOG.Errorf("load qqwry datas failed: %s", err)
}
var itemLogs []dto.SSHHistory
for i := 0; i < len(data.Logs); i++ {
data.Logs[i].Area = qqWry.Find(data.Logs[i].Address).Area
data.Logs[i].Date, _ = time.ParseInLocation("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s", timeNow.Year(), data.Logs[i].DateStr), nyc)
itemLogs = append(itemLogs, data.Logs[i])
for _, file := range fileList {
commandItem := ""
if strings.HasPrefix(path.Base(file.Name), "secure") {
switch req.Status {
case constant.StatusSuccess:
commandItem = fmt.Sprintf("cat %s | grep -a Accepted %s", file.Name, command)
case constant.StatusFailed:
commandItem = fmt.Sprintf("cat %s | grep -a 'Failed password for' | grep -v 'invalid' %s", file.Name, command)
default:
commandItem = fmt.Sprintf("cat %s | grep -aE '(Failed password for|Accepted)' | grep -v 'invalid' %s", file.Name, command)
}
}
if strings.HasPrefix(path.Base(file.Name), "auth.log") {
switch req.Status {
case constant.StatusSuccess:
commandItem = fmt.Sprintf("cat %s | grep -a Accepted %s", file.Name, command)
case constant.StatusFailed:
commandItem = fmt.Sprintf("cat %s | grep -a 'Connection closed by authenticating user' | grep -a 'preauth' %s", file.Name, command)
default:
commandItem = fmt.Sprintf("cat %s | grep -aE \"(Connection closed by authenticating user|Accepted)\" | grep -v 'invalid' %s", file.Name, command)
}
}
dataItem, successCount, failedCount := loadSSHData(commandItem, showCountFrom, showCountTo, file.Year, qqWry, nyc)
data.FailedCount += failedCount
data.TotalCount += successCount + failedCount
showCountFrom = showCountFrom - (successCount + failedCount)
showCountTo = showCountTo - (successCount + failedCount)
data.Logs = append(data.Logs, dataItem...)
}
data.Logs = itemLogs
data.SuccessfulCount = data.TotalCount - data.FailedCount
return &data, nil
}
func sortFileList(fileNames []string) []string {
func (u *SSHService) LoadSSHConf() (string, error) {
if _, err := os.Stat("/etc/ssh/sshd_config"); err != nil {
return "", buserr.New("ErrHttpReqNotFound")
}
content, err := os.ReadFile("/etc/ssh/sshd_config")
if err != nil {
return "", err
}
return string(content), nil
}
func sortFileList(fileNames []sshFileItem) []sshFileItem {
if len(fileNames) < 2 {
return fileNames
}
if strings.HasPrefix(path.Base(fileNames[0]), "secure") {
var itemFile []string
if strings.HasPrefix(path.Base(fileNames[0].Name), "secure") {
var itemFile []sshFileItem
sort.Slice(fileNames, func(i, j int) bool {
return fileNames[i] > fileNames[j]
return fileNames[i].Name > fileNames[j].Name
})
itemFile = append(itemFile, fileNames[len(fileNames)-1])
itemFile = append(itemFile, fileNames[:len(fileNames)-2]...)
return itemFile
}
sort.Slice(fileNames, func(i, j int) bool {
return fileNames[i] < fileNames[j]
return fileNames[i].Name < fileNames[j].Name
})
return fileNames
}
@@ -338,109 +363,111 @@ func updateSSHConf(oldFiles []string, param string, value interface{}) []string
return newFiles
}
func loadSuccessDatas(command string, withAppend bool) ([]dto.SSHHistory, int) {
func loadSSHData(command string, showCountFrom, showCountTo, currentYear int, qqWry *qqwry.QQwry, nyc *time.Location) ([]dto.SSHHistory, int, int) {
var (
datas []dto.SSHHistory
totalNum int
datas []dto.SSHHistory
successCount int
failedCount int
)
stdout2, err := cmd.Exec(command)
if err == nil {
lines := strings.Split(string(stdout2), "\n")
if len(lines) == 0 {
return datas, 0
}
for i := len(lines) - 1; i >= 0; i-- {
parts := strings.Fields(lines[i])
if len(parts) < 14 {
continue
}
totalNum++
if withAppend {
historyItem := dto.SSHHistory{
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
AuthMode: parts[6],
User: parts[8],
Address: parts[10],
Port: parts[12],
Status: constant.StatusSuccess,
if err != nil {
return datas, 0, 0
}
lines := strings.Split(string(stdout2), "\n")
for i := len(lines) - 1; i >= 0; i-- {
var itemData dto.SSHHistory
switch {
case strings.Contains(lines[i], "Failed password for"):
itemData = loadFailedSecureDatas(lines[i])
if len(itemData.Address) != 0 {
if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo {
itemData.Area = qqWry.Find(itemData.Address).Area
itemData.Date, _ = time.ParseInLocation("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s", currentYear, itemData.DateStr), nyc)
datas = append(datas, itemData)
}
datas = append(datas, historyItem)
failedCount++
}
case strings.Contains(lines[i], "Connection closed by authenticating user"):
itemData = loadFailedAuthDatas(lines[i])
if len(itemData.Address) != 0 {
if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo {
itemData.Area = qqWry.Find(itemData.Address).Area
itemData.Date, _ = time.ParseInLocation("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s", currentYear, itemData.DateStr), nyc)
datas = append(datas, itemData)
}
failedCount++
}
case strings.Contains(lines[i], "Accepted "):
itemData = loadSuccessDatas(lines[i])
if len(itemData.Address) != 0 {
if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo {
itemData.Area = qqWry.Find(itemData.Address).Area
itemData.Date, _ = time.ParseInLocation("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s", currentYear, itemData.DateStr), nyc)
datas = append(datas, itemData)
}
successCount++
}
}
}
return datas, totalNum
return datas, successCount, failedCount
}
func loadFailedAuthDatas(command string, withAppend bool) ([]dto.SSHHistory, int) {
var (
datas []dto.SSHHistory
totalNum int
)
stdout2, err := cmd.Exec(command)
if err == nil {
lines := strings.Split(string(stdout2), "\n")
if len(lines) == 0 {
return datas, 0
}
for i := len(lines) - 1; i >= 0; i-- {
parts := strings.Fields(lines[i])
if len(parts) < 14 {
continue
}
totalNum++
if withAppend {
historyItem := dto.SSHHistory{
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
AuthMode: parts[8],
User: parts[10],
Address: parts[11],
Port: parts[13],
Status: constant.StatusFailed,
}
if strings.Contains(lines[i], ": ") {
historyItem.Message = strings.Split(lines[i], ": ")[1]
}
datas = append(datas, historyItem)
}
}
func loadSuccessDatas(line string) dto.SSHHistory {
var data dto.SSHHistory
parts := strings.Fields(line)
if len(parts) < 14 {
return data
}
return datas, totalNum
data = dto.SSHHistory{
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
AuthMode: parts[6],
User: parts[8],
Address: parts[10],
Port: parts[12],
Status: constant.StatusSuccess,
}
return data
}
func loadFailedSecureDatas(command string, withAppend bool) ([]dto.SSHHistory, int) {
var (
datas []dto.SSHHistory
totalNum int
)
stdout2, err := cmd.Exec(command)
if err == nil {
lines := strings.Split(string(stdout2), "\n")
if len(lines) == 0 {
return datas, 0
}
for i := len(lines) - 1; i >= 0; i-- {
parts := strings.Fields(lines[i])
if len(parts) < 14 {
continue
}
totalNum++
if withAppend {
historyItem := dto.SSHHistory{
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
AuthMode: parts[6],
User: parts[8],
Address: parts[10],
Port: parts[12],
Status: constant.StatusFailed,
}
if strings.Contains(lines[i], ": ") {
historyItem.Message = strings.Split(lines[i], ": ")[1]
}
datas = append(datas, historyItem)
}
}
func loadFailedAuthDatas(line string) dto.SSHHistory {
var data dto.SSHHistory
parts := strings.Fields(line)
if len(parts) < 14 {
return data
}
return datas, totalNum
data = dto.SSHHistory{
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
AuthMode: parts[8],
User: parts[10],
Address: parts[11],
Port: parts[13],
Status: constant.StatusFailed,
}
if strings.Contains(line, ": ") {
data.Message = strings.Split(line, ": ")[1]
}
return data
}
func loadFailedSecureDatas(line string) dto.SSHHistory {
var data dto.SSHHistory
parts := strings.Fields(line)
if len(parts) < 14 {
return data
}
data = dto.SSHHistory{
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
AuthMode: parts[6],
User: parts[8],
Address: parts[10],
Port: parts[12],
Status: constant.StatusFailed,
}
if strings.Contains(line, ": ") {
data.Message = strings.Split(line, ": ")[1]
}
return data
}
func handleGunzip(path string) error {
@@ -449,3 +476,12 @@ func handleGunzip(path string) error {
}
return nil
}
func loadServiceName() (string, error) {
if exist, _ := systemctl.IsExist("sshd"); exist {
return "sshd", nil
} else if exist, _ := systemctl.IsExist("ssh"); exist {
return "ssh", nil
}
return "", errors.New("The ssh or sshd service is unavailable")
}
+18 -13
View File
@@ -6,6 +6,7 @@ import (
"io"
"net/http"
"os"
"path"
"runtime"
"strings"
"time"
@@ -86,8 +87,8 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
global.LOG.Info("start to upgrade now...")
fileOp := files.NewFileOp()
timeStr := time.Now().Format("20060102150405")
rootDir := fmt.Sprintf("%s/upgrade_%s/downloads", global.CONF.System.TmpDir, timeStr)
originalDir := fmt.Sprintf("%s/upgrade_%s/original", global.CONF.System.TmpDir, timeStr)
rootDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("upgrade/upgrade_%s/downloads", timeStr))
originalDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("upgrade/upgrade_%s/original", timeStr))
if err := os.MkdirAll(rootDir, os.ModePerm); err != nil {
return err
}
@@ -103,6 +104,10 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
fileName := fmt.Sprintf("1panel-%s-%s-%s.tar.gz", req.Version, "linux", itemArch)
_ = settingRepo.Update("SystemStatus", "Upgrading")
go func() {
_ = global.Cron.Stop()
defer func() {
global.Cron.Start()
}()
if err := fileOp.DownloadFile(downloadPath+"/"+fileName, rootDir+"/"+fileName); err != nil {
global.LOG.Errorf("download service file failed, err: %v", err)
_ = settingRepo.Update("SystemStatus", "Free")
@@ -126,26 +131,26 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
}
global.LOG.Info("backup original data successful, now start to upgrade!")
if err := cpBinary(tmpDir+"/1panel", "/usr/local/bin/1panel"); err != nil {
u.handleRollback(fileOp, originalDir, 1)
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)
return
}
if err := cpBinary(tmpDir+"/1pctl", "/usr/local/bin/1pctl"); err != nil {
u.handleRollback(fileOp, originalDir, 2)
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)
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 {
u.handleRollback(fileOp, originalDir, 2)
global.LOG.Errorf("upgrade basedir in 1pctl failed, err: %v", err)
u.handleRollback(fileOp, originalDir, 2)
return
}
if err := cpBinary(tmpDir+"/1panel.service", "/etc/systemd/system/1panel.service"); err != nil {
u.handleRollback(fileOp, originalDir, 3)
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)
return
}
@@ -178,22 +183,22 @@ func (u *UpgradeService) handleBackup(fileOp files.FileOp, originalDir string) e
func (u *UpgradeService) handleRollback(fileOp files.FileOp, originalDir string, errStep int) {
dbPath := global.CONF.System.DbPath + "/1Panel.db"
_ = settingRepo.Update("SystemStatus", "Free")
if err := cpBinary(originalDir+"/1Panel.db", dbPath); err != nil {
if err := cpBinary([]string{originalDir + "/1Panel.db"}, dbPath); err != nil {
global.LOG.Errorf("rollback 1panel failed, err: %v", err)
}
if err := cpBinary(originalDir+"/1panel", "/usr/local/bin/1panel"); err != nil {
if err := cpBinary([]string{originalDir + "/1panel"}, "/usr/local/bin/1panel"); err != nil {
global.LOG.Errorf("rollback 1pctl failed, err: %v", err)
}
if errStep == 1 {
return
}
if err := cpBinary(originalDir+"/1pctl", "/usr/local/bin/1pctl"); err != nil {
if err := cpBinary([]string{originalDir + "/1pctl"}, "/usr/local/bin/1pctl"); err != nil {
global.LOG.Errorf("rollback 1panel failed, err: %v", err)
}
if errStep == 2 {
return
}
if err := cpBinary(originalDir+"/1panel.service", "/etc/systemd/system/1panel.service"); err != nil {
if err := cpBinary([]string{originalDir + "/1panel.service"}, "/etc/systemd/system/1panel.service"); err != nil {
global.LOG.Errorf("rollback 1panel failed, err: %v", err)
}
}
+562 -33
View File
@@ -5,6 +5,7 @@ import (
"bytes"
"context"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
@@ -14,11 +15,18 @@ import (
"regexp"
"strconv"
"strings"
"syscall"
"time"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/spf13/afero"
"github.com/1Panel-dev/1Panel/backend/utils/compose"
"github.com/1Panel-dev/1Panel/backend/utils/env"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/nginx"
"github.com/1Panel-dev/1Panel/backend/utils/nginx/components"
"github.com/1Panel-dev/1Panel/backend/utils/nginx/parser"
@@ -54,22 +62,29 @@ type IWebsiteService interface {
CreateWebsiteDomain(create request.WebsiteDomainCreate) (model.WebsiteDomain, error)
GetWebsiteDomain(websiteId uint) ([]model.WebsiteDomain, error)
DeleteWebsiteDomain(domainId uint) error
GetNginxConfigByScope(req request.NginxScopeReq) (*response.WebsiteNginxConfig, error)
UpdateNginxConfigByScope(req request.NginxConfigUpdate) error
GetWebsiteNginxConfig(websiteId uint, configType string) (response.FileInfo, error)
UpdateNginxConfigFile(req request.WebsiteNginxUpdate) error
GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS, error)
OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (*response.WebsiteHTTPS, error)
PreInstallCheck(req request.WebsiteInstallCheckReq) ([]response.WebsitePreInstallCheck, error)
GetWafConfig(req request.WebsiteWafReq) (response.WebsiteWafConfig, error)
UpdateWafConfig(req request.WebsiteWafUpdate) error
UpdateNginxConfigFile(req request.WebsiteNginxUpdate) error
OpWebsiteLog(req request.WebsiteLogReq) (*response.WebsiteLog, error)
ChangeDefaultServer(id uint) error
PreInstallCheck(req request.WebsiteInstallCheckReq) ([]response.WebsitePreInstallCheck, error)
GetWafConfig(req request.WebsiteWafReq) (response.WebsiteWafConfig, error)
UpdateWafConfig(req request.WebsiteWafUpdate) error
UpdateWafFile(req request.WebsiteWafFileUpdate) (err error)
GetPHPConfig(id uint) (*response.PHPConfig, error)
UpdatePHPConfig(req request.WebsitePHPConfigUpdate) error
UpdatePHPConfigFile(req request.WebsitePHPFileUpdate) error
ChangePHPVersion(req request.WebsitePHPVersionReq) error
GetRewriteConfig(req request.NginxRewriteReq) (*response.NginxRewriteRes, error)
UpdateRewriteConfig(req request.NginxRewriteUpdate) error
LoadWebsiteDirConfig(req request.WebsiteCommonReq) (*response.WebsiteDirConfig, error)
UpdateSiteDir(req request.WebsiteUpdateDir) error
UpdateSitePermission(req request.WebsiteUpdateDirPermission) error
OperateProxy(req request.WebsiteProxyConfig) (err error)
@@ -79,6 +94,9 @@ type IWebsiteService interface {
UpdateAuthBasic(req request.NginxAuthUpdate) (err error)
GetAntiLeech(id uint) (*response.NginxAntiLeechRes, error)
UpdateAntiLeech(req request.NginxAntiLeechUpdate) (err error)
OperateRedirect(req request.NginxRedirectReq) (err error)
GetRedirect(id uint) (res []response.NginxRedirectConfig, err error)
UpdateRedirectFile(req request.NginxRedirectUpdate) (err error)
}
func NewIWebsiteService() IWebsiteService {
@@ -162,6 +180,11 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
if exist, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithDomain(create.PrimaryDomain)); len(exist) > 0 {
return buserr.New(constant.ErrDomainIsExist)
}
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return err
}
defaultHttpPort := nginxInstall.HttpPort
defaultDate, _ := time.Parse(constant.DateLayout, constant.DefaultDate)
website := &model.Website{
@@ -278,13 +301,13 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
}
var domains []model.WebsiteDomain
domains = append(domains, model.WebsiteDomain{Domain: website.PrimaryDomain, Port: 80})
domains = append(domains, model.WebsiteDomain{Domain: website.PrimaryDomain, Port: defaultHttpPort})
otherDomainArray := strings.Split(create.OtherDomains, "\n")
for _, domain := range otherDomainArray {
if domain == "" {
continue
}
domainModel, err := getDomain(domain)
domainModel, err := getDomain(domain, defaultHttpPort)
if err != nil {
return err
}
@@ -339,6 +362,11 @@ func (w WebsiteService) UpdateWebsite(req request.WebsiteUpdate) error {
if err != nil {
return err
}
if website.IPV6 != req.IPV6 {
if err := changeIPV6(website, req.IPV6); err != nil {
return err
}
}
website.PrimaryDomain = req.PrimaryDomain
website.WebsiteGroupID = req.WebsiteGroupID
website.Remark = req.Remark
@@ -408,13 +436,13 @@ func (w WebsiteService) DeleteWebsite(req request.WebsiteDelete) error {
if req.DeleteBackup {
localDir, _ := loadLocalDir()
backupDir := fmt.Sprintf("%s/website/%s", localDir, website.Alias)
backupDir := path.Join(localDir, fmt.Sprintf("website/%s", website.Alias))
if _, err := os.Stat(backupDir); err == nil {
_ = os.RemoveAll(backupDir)
}
global.LOG.Infof("delete website %s backups successful", website.Alias)
}
uploadDir := fmt.Sprintf("%s/1panel/uploads/website/%s", global.CONF.System.BaseDir, website.Alias)
uploadDir := path.Join(global.CONF.System.BaseDir, fmt.Sprintf("1panel/uploads/website/%s", website.Alias))
if _, err := os.Stat(uploadDir); err == nil {
_ = os.RemoveAll(uploadDir)
}
@@ -427,11 +455,20 @@ func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate)
ports []int
domains []string
)
if create.Port != 80 {
httpPort, _, err := getAppInstallPort(constant.AppOpenresty)
if err != nil {
return domainModel, err
}
if existDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithPort(create.Port)); len(existDomains) == 0 {
if existAppInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithPort(create.Port)); !reflect.DeepEqual(existAppInstall, model.AppInstall{}) {
return domainModel, buserr.WithMap(constant.ErrPortInOtherApp, map[string]interface{}{"port": create.Port, "apps": existAppInstall.App.Name}, nil)
}
if common.ScanPort(create.Port) {
return domainModel, buserr.WithDetail(constant.ErrPortInUsed, create.Port, nil)
}
}
website, err := websiteRepo.GetFirst(commonRepo.WithByID(create.WebsiteID))
if err != nil {
return domainModel, err
@@ -448,7 +485,7 @@ func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate)
Port: create.Port,
WebsiteID: create.WebsiteID,
}
if create.Port != 80 {
if create.Port != httpPort {
go func() {
_ = OperateFirewallPort(nil, []int{create.Port})
}()
@@ -634,7 +671,12 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH
if !req.Enable {
website.Protocol = constant.ProtocolHTTP
website.WebsiteSSLID = 0
if err := deleteListenAndServerName(website, []string{"443", "[::]:443"}, []string{}); err != nil {
_, httpsPort, err := getAppInstallPort(constant.AppOpenresty)
if err != nil {
return nil, err
}
httpsPortStr := strconv.Itoa(httpsPort)
if err := deleteListenAndServerName(website, []string{httpsPortStr, "[::]:" + httpsPortStr}, []string{}); err != nil {
return nil, err
}
nginxParams := getNginxParamsFromStaticFile(dto.SSL, nil)
@@ -775,7 +817,7 @@ func (w WebsiteService) PreInstallCheck(req request.WebsiteInstallCheckReq) ([]r
checkIds = append(req.InstallIds, appInstall.ID)
}
for _, id := range checkIds {
if err := syncById(id); err != nil {
if err := syncByID(id); err != nil {
return nil, err
}
}
@@ -831,7 +873,6 @@ func (w WebsiteService) GetWafConfig(req request.WebsiteWafReq) (response.Websit
if err != nil {
return res, nil
}
res.FilePath = filePath
res.Content = string(content)
return res, nil
@@ -895,19 +936,13 @@ func (w WebsiteService) OpWebsiteLog(req request.WebsiteLogReq) (*response.Websi
}
}
filePath := path.Join(sitePath, "log", req.LogType)
fileInfo, err := os.Stat(filePath)
lines, end, err := files.ReadFileByLine(filePath, req.Page, req.PageSize)
if err != nil {
return nil, err
}
if fileInfo.Size() > 20<<20 {
return nil, buserr.New(constant.ErrFileToLarge)
}
fileInfo.Size()
content, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
res.Content = string(content)
res.End = end
res.Path = filePath
res.Content = strings.Join(lines, "\n")
return res, nil
case constant.DisableLog:
key := "access_log"
@@ -990,12 +1025,17 @@ func (w WebsiteService) ChangeDefaultServer(id uint) error {
if err != nil {
return err
}
httpPort, httpsPort, err := getAppInstallPort(constant.AppOpenresty)
if err != nil {
return err
}
var changeParams []dto.NginxParam
for _, param := range params {
paramLen := len(param.Params)
bind := param.Params[0]
var newParam []string
if bind == "80" || bind == "443" || bind == "[::]:80" || bind == "[::]:443" {
if bind == strconv.Itoa(httpPort) || bind == strconv.Itoa(httpsPort) || bind == "[::]:"+strconv.Itoa(httpPort) || bind == "[::]:"+strconv.Itoa(httpsPort) {
if param.Params[paramLen-1] == components.DefaultServer {
newParam = param.Params
} else {
@@ -1028,7 +1068,7 @@ func (w WebsiteService) GetPHPConfig(id uint) (*response.PHPConfig, error) {
phpConfigPath := path.Join(appInstall.GetPath(), "conf", "php.ini")
fileOp := files.NewFileOp()
if !fileOp.Stat(phpConfigPath) {
return nil, buserr.WithDetail(constant.ErrFileCanNotRead, "php.ini", nil)
return nil, buserr.WithMap("ErrFileNotFound", map[string]interface{}{"name": "php.ini"}, nil)
}
params := make(map[string]string)
configFile, err := fileOp.OpenFile(phpConfigPath)
@@ -1082,7 +1122,7 @@ func (w WebsiteService) UpdatePHPConfig(req request.WebsitePHPConfigUpdate) (err
phpConfigPath := path.Join(appInstall.GetPath(), "conf", "php.ini")
fileOp := files.NewFileOp()
if !fileOp.Stat(phpConfigPath) {
return buserr.WithDetail(constant.ErrFileCanNotRead, "php.ini", nil)
return buserr.WithMap("ErrFileNotFound", map[string]interface{}{"name": "php.ini"}, nil)
}
configFile, err := fileOp.OpenFile(phpConfigPath)
if err != nil {
@@ -1171,9 +1211,114 @@ func (w WebsiteService) UpdatePHPConfigFile(req request.WebsitePHPFileUpdate) er
if err := files.NewFileOp().WriteFile(configPath, strings.NewReader(req.Content), 0755); err != nil {
return err
}
if _, err := compose.Restart(runtimeInstall.GetComposePath()); err != nil {
return err
}
return nil
}
func (w WebsiteService) ChangePHPVersion(req request.WebsitePHPVersionReq) error {
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
if err != nil {
return err
}
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(req.RuntimeID))
if err != nil {
return err
}
oldRuntime, err := runtimeRepo.GetFirst(commonRepo.WithByID(req.RuntimeID))
if err != nil {
return err
}
if runtime.Resource == constant.ResourceLocal || oldRuntime.Resource == constant.ResourceLocal {
return buserr.New("ErrPHPResource")
}
appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
if err != nil {
return err
}
appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(runtime.AppDetailID))
if err != nil {
return err
}
envs := make(map[string]interface{})
if err = json.Unmarshal([]byte(appInstall.Env), &envs); err != nil {
return err
}
if out, err := compose.Down(appInstall.GetComposePath()); err != nil {
if out != "" {
return errors.New(out)
}
return err
}
var (
busErr error
fileOp = files.NewFileOp()
envPath = appInstall.GetEnvPath()
composePath = appInstall.GetComposePath()
confDir = path.Join(appInstall.GetPath(), "conf")
backupConfDir = path.Join(appInstall.GetPath(), "conf_bak")
fpmConfDir = path.Join(confDir, "php-fpm.conf")
phpDir = path.Join(constant.RuntimeDir, runtime.Type, runtime.Name, "php")
oldFmContent, _ = fileOp.GetContent(fpmConfDir)
)
envParams := make(map[string]string, len(envs))
handleMap(envs, envParams)
envParams["IMAGE_NAME"] = runtime.Image
defer func() {
if busErr != nil {
envParams["IMAGE_NAME"] = oldRuntime.Image
_ = env.Write(envParams, envPath)
_ = fileOp.WriteFile(composePath, strings.NewReader(appInstall.DockerCompose), 0775)
if fileOp.Stat(backupConfDir) {
_ = fileOp.DeleteDir(confDir)
_ = fileOp.Rename(backupConfDir, confDir)
}
}
}()
if busErr = env.Write(envParams, envPath); busErr != nil {
return busErr
}
if busErr = fileOp.WriteFile(composePath, strings.NewReader(appDetail.DockerCompose), 0775); busErr != nil {
return busErr
}
if !req.RetainConfig {
if busErr = fileOp.Rename(confDir, backupConfDir); busErr != nil {
return busErr
}
_ = fileOp.CreateDir(confDir, 0755)
if busErr = fileOp.CopyFile(path.Join(phpDir, "php-fpm.conf"), confDir); busErr != nil {
return busErr
}
if busErr = fileOp.CopyFile(path.Join(phpDir, "php.ini"), confDir); busErr != nil {
_ = fileOp.WriteFile(fpmConfDir, bytes.NewReader(oldFmContent), 0775)
return busErr
}
}
if out, err := compose.Up(appInstall.GetComposePath()); err != nil {
if out != "" {
busErr = errors.New(out)
return busErr
}
busErr = err
return busErr
}
_ = fileOp.DeleteDir(backupConfDir)
appInstall.AppDetailId = runtime.AppDetailID
appInstall.AppId = appDetail.AppId
appInstall.Version = appDetail.Version
appInstall.DockerCompose = appDetail.DockerCompose
_ = appInstallRepo.Save(context.Background(), &appInstall)
website.RuntimeID = req.RuntimeID
return websiteRepo.Save(context.Background(), &website)
}
func (w WebsiteService) UpdateRewriteConfig(req request.NginxRewriteUpdate) error {
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
if err != nil {
@@ -1253,7 +1398,7 @@ func (w WebsiteService) UpdateSiteDir(req request.WebsiteUpdateDir) error {
runDir := req.SiteDir
siteDir := path.Join("/www/sites", website.Alias, "index")
if req.SiteDir != "/" {
siteDir = fmt.Sprintf("%s/%s", siteDir, req.SiteDir)
siteDir = fmt.Sprintf("%s%s", siteDir, req.SiteDir)
}
if err := updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "root", Params: []string{siteDir}}}, &website); err != nil {
return err
@@ -1272,9 +1417,6 @@ func (w WebsiteService) UpdateSitePermission(req request.WebsiteUpdateDirPermiss
return err
}
absoluteIndexPath := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "index")
if website.SiteDir != "/" {
absoluteIndexPath = path.Join(absoluteIndexPath, website.SiteDir)
}
chownCmd := fmt.Sprintf("chown -R %s:%s %s", req.User, req.Group, absoluteIndexPath)
if cmd.HasNoPasswordSudo() {
chownCmd = fmt.Sprintf("sudo %s", chownCmd)
@@ -1690,7 +1832,7 @@ func (w WebsiteService) UpdateAntiLeech(req request.NginxAntiLeechUpdate) (err e
return
}
fileOp := files.NewFileOp()
backpContent, err := fileOp.GetContent(nginxFull.SiteConfig.Config.FilePath)
backupContent, err := fileOp.GetContent(nginxFull.SiteConfig.Config.FilePath)
if err != nil {
return
}
@@ -1761,7 +1903,7 @@ func (w WebsiteService) UpdateAntiLeech(req request.NginxAntiLeechUpdate) (err e
return
}
if err = updateNginxConfig(constant.NginxScopeServer, nil, &website); err != nil {
_ = fileOp.WriteFile(nginxFull.SiteConfig.Config.FilePath, bytes.NewReader(backpContent), 0755)
_ = fileOp.WriteFile(nginxFull.SiteConfig.Config.FilePath, bytes.NewReader(backupContent), 0755)
return
}
return
@@ -1844,3 +1986,390 @@ func (w WebsiteService) GetAntiLeech(id uint) (*response.NginxAntiLeechRes, erro
}
return res, nil
}
func (w WebsiteService) OperateRedirect(req request.NginxRedirectReq) (err error) {
var (
website model.Website
nginxInstall model.AppInstall
oldContent []byte
)
website, err = websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
if err != nil {
return err
}
nginxInstall, err = getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return
}
includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "redirect")
fileOp := files.NewFileOp()
if !fileOp.Stat(includeDir) {
_ = fileOp.CreateDir(includeDir, 0755)
}
fileName := fmt.Sprintf("%s.conf", req.Name)
includePath := path.Join(includeDir, fileName)
backName := fmt.Sprintf("%s.bak", req.Name)
backPath := path.Join(includeDir, backName)
if req.Operate == "create" && (fileOp.Stat(includePath) || fileOp.Stat(backPath)) {
err = buserr.New(constant.ErrNameIsExist)
return
}
defer func() {
if err != nil {
switch req.Operate {
case "create":
_ = fileOp.DeleteFile(includePath)
case "edit":
_ = fileOp.WriteFile(includePath, bytes.NewReader(oldContent), 0755)
}
}
}()
var (
config *components.Config
oldPar *parser.Parser
)
switch req.Operate {
case "create":
config = &components.Config{}
case "edit":
oldPar, err = parser.NewParser(includePath)
if err != nil {
return
}
config = oldPar.Parse()
oldContent, err = fileOp.GetContent(includePath)
if err != nil {
return
}
case "delete":
_ = fileOp.DeleteFile(includePath)
_ = fileOp.DeleteFile(backPath)
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
case "disable":
_ = fileOp.Rename(includePath, backPath)
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
case "enable":
_ = fileOp.Rename(backPath, includePath)
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
}
target := req.Target
block := &components.Block{}
switch req.Type {
case "path":
if req.KeepPath {
target = req.Target + "$1"
} else {
target = req.Target + "?"
}
redirectKey := "permanent"
if req.Redirect == "302" {
redirectKey = "redirect"
}
block = &components.Block{
Directives: []components.IDirective{
&components.Directive{
Name: "rewrite",
Parameters: []string{fmt.Sprintf("^%s(.*)", req.Path), target, redirectKey},
},
},
}
case "domain":
if req.KeepPath {
target = req.Target + "$request_uri"
}
returnBlock := &components.Block{
Directives: []components.IDirective{
&components.Directive{
Name: "return",
Parameters: []string{req.Redirect, target},
},
},
}
for _, domain := range req.Domains {
block.Directives = append(block.Directives, &components.Directive{
Name: "if",
Parameters: []string{"($host", "~", fmt.Sprintf("'^%s')", domain)},
Block: returnBlock,
})
}
case "404":
if req.KeepPath && !req.RedirectRoot {
target = req.Target + "$request_uri"
}
if req.RedirectRoot {
target = "/"
} else {
if req.KeepPath {
target = req.Target + "$request_uri"
}
}
block = &components.Block{
Directives: []components.IDirective{
&components.Directive{
Name: "error_page",
Parameters: []string{"404", "=", "@notfound"},
},
&components.Directive{
Name: "location",
Parameters: []string{"@notfound"},
Block: &components.Block{
Directives: []components.IDirective{
&components.Directive{
Name: "return",
Parameters: []string{"301", target},
},
},
},
},
},
}
}
config.FilePath = includePath
config.Block = block
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
return buserr.WithErr(constant.ErrUpdateBuWebsite, err)
}
nginxInclude := fmt.Sprintf("/www/sites/%s/redirect/*.conf", website.Alias)
if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil {
return
}
return
}
func (w WebsiteService) GetRedirect(id uint) (res []response.NginxRedirectConfig, err error) {
var (
website model.Website
nginxInstall model.AppInstall
fileList response.FileInfo
)
website, err = websiteRepo.GetFirst(commonRepo.WithByID(id))
if err != nil {
return
}
nginxInstall, err = getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return
}
includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "redirect")
fileOp := files.NewFileOp()
if !fileOp.Stat(includeDir) {
return
}
fileList, err = NewIFileService().GetFileList(request.FileOption{FileOption: files.FileOption{Path: includeDir, Expand: true, Page: 1, PageSize: 100}})
if len(fileList.Items) == 0 {
return
}
var (
content []byte
config *components.Config
)
for _, configFile := range fileList.Items {
redirectConfig := response.NginxRedirectConfig{
WebsiteID: website.ID,
}
parts := strings.Split(configFile.Name, ".")
redirectConfig.Name = parts[0]
if parts[1] == "conf" {
redirectConfig.Enable = true
} else {
redirectConfig.Enable = false
}
redirectConfig.FilePath = configFile.Path
content, err = fileOp.GetContent(configFile.Path)
if err != nil {
return
}
redirectConfig.Content = string(content)
config = parser.NewStringParser(string(content)).Parse()
dirs := config.GetDirectives()
if len(dirs) > 0 {
firstName := dirs[0].GetName()
switch firstName {
case "if":
for _, ifDir := range dirs {
params := ifDir.GetParameters()
if len(params) > 2 && params[0] == "($host" {
domain := strings.Trim(strings.Trim(params[2], "'"), "^")
redirectConfig.Domains = append(redirectConfig.Domains, domain)
if len(redirectConfig.Domains) > 1 {
continue
}
redirectConfig.Type = "domain"
}
childDirs := ifDir.GetBlock().GetDirectives()
for _, dir := range childDirs {
if dir.GetName() == "return" {
dirParams := dir.GetParameters()
if len(dirParams) > 1 {
redirectConfig.Redirect = dirParams[0]
if strings.HasSuffix(dirParams[1], "$request_uri") {
redirectConfig.KeepPath = true
redirectConfig.Target = strings.TrimSuffix(dirParams[1], "$request_uri")
} else {
redirectConfig.KeepPath = false
redirectConfig.Target = dirParams[1]
}
}
}
}
}
case "rewrite":
redirectConfig.Type = "path"
for _, pathDir := range dirs {
if pathDir.GetName() == "rewrite" {
params := pathDir.GetParameters()
if len(params) > 2 {
redirectConfig.Path = strings.Trim(strings.Trim(params[0], "^"), "(.*)")
if strings.HasSuffix(params[1], "$1") {
redirectConfig.KeepPath = true
redirectConfig.Target = strings.TrimSuffix(params[1], "$1")
} else {
redirectConfig.KeepPath = false
redirectConfig.Target = strings.TrimSuffix(params[1], "?")
}
if params[2] == "permanent" {
redirectConfig.Redirect = "301"
} else {
redirectConfig.Redirect = "302"
}
}
}
}
case "error_page":
redirectConfig.Type = "404"
for _, errDir := range dirs {
if errDir.GetName() == "location" {
childDirs := errDir.GetBlock().GetDirectives()
for _, dir := range childDirs {
if dir.GetName() == "return" {
dirParams := dir.GetParameters()
if len(dirParams) > 1 {
redirectConfig.Redirect = dirParams[0]
if strings.HasSuffix(dirParams[1], "$request_uri") {
redirectConfig.KeepPath = true
redirectConfig.Target = strings.TrimSuffix(dirParams[1], "$request_uri")
redirectConfig.RedirectRoot = false
} else {
redirectConfig.KeepPath = false
redirectConfig.Target = dirParams[1]
redirectConfig.RedirectRoot = redirectConfig.Target == "/"
}
}
}
}
}
}
}
}
res = append(res, redirectConfig)
}
return
}
func (w WebsiteService) UpdateRedirectFile(req request.NginxRedirectUpdate) (err error) {
var (
website model.Website
nginxFull dto.NginxFull
oldRewriteContent []byte
)
website, err = websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
if err != nil {
return err
}
nginxFull, err = getNginxFull(&website)
if err != nil {
return err
}
includePath := fmt.Sprintf("/www/sites/%s/redirect/%s.conf", website.Alias, req.Name)
absolutePath := path.Join(nginxFull.Install.GetPath(), includePath)
fileOp := files.NewFileOp()
oldRewriteContent, err = fileOp.GetContent(absolutePath)
if err != nil {
return err
}
if err = fileOp.WriteFile(absolutePath, strings.NewReader(req.Content), 0755); err != nil {
return err
}
defer func() {
if err != nil {
_ = fileOp.WriteFile(absolutePath, bytes.NewReader(oldRewriteContent), 0755)
}
}()
return updateNginxConfig(constant.NginxScopeServer, nil, &website)
}
func (w WebsiteService) UpdateWafFile(req request.WebsiteWafFileUpdate) (err error) {
var (
website model.Website
nginxInstall model.AppInstall
)
website, err = websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
if err != nil {
return err
}
nginxInstall, err = getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return
}
rulePath := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "waf", "rules", fmt.Sprintf("%s.json", req.Type))
return files.NewFileOp().WriteFile(rulePath, strings.NewReader(req.Content), 0755)
}
func (w WebsiteService) LoadWebsiteDirConfig(req request.WebsiteCommonReq) (*response.WebsiteDirConfig, error) {
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID))
if err != nil {
return nil, err
}
res := &response.WebsiteDirConfig{}
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return nil, err
}
absoluteIndexPath := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "index")
var appFs = afero.NewOsFs()
info, err := appFs.Stat(absoluteIndexPath)
if err != nil {
return nil, err
}
res.User = strconv.FormatUint(uint64(info.Sys().(*syscall.Stat_t).Uid), 10)
res.UserGroup = strconv.FormatUint(uint64(info.Sys().(*syscall.Stat_t).Gid), 10)
indexFiles, err := os.ReadDir(absoluteIndexPath)
if err != nil {
return nil, err
}
res.Dirs = []string{"/"}
for _, file := range indexFiles {
if !file.IsDir() {
continue
}
res.Dirs = append(res.Dirs, fmt.Sprintf("/%s", file.Name()))
fileInfo, _ := file.Info()
if fileInfo.Sys().(*syscall.Stat_t).Uid != 1000 || fileInfo.Sys().(*syscall.Stat_t).Gid != 1000 {
res.Msg = i18n.GetMsgByKey("ErrPathPermission")
}
childFiles, _ := os.ReadDir(absoluteIndexPath + "/" + file.Name())
for _, childFile := range childFiles {
if !childFile.IsDir() {
continue
}
childInfo, _ := childFile.Info()
if childInfo.Sys().(*syscall.Stat_t).Uid != 1000 || childInfo.Sys().(*syscall.Stat_t).Gid != 1000 {
res.Msg = i18n.GetMsgByKey("ErrPathPermission")
}
res.Dirs = append(res.Dirs, fmt.Sprintf("/%s/%s", file.Name(), childFile.Name()))
}
}
return res, nil
}
+10 -2
View File
@@ -13,6 +13,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/ssl"
"path"
"strconv"
"strings"
)
@@ -66,7 +67,14 @@ func (w WebsiteSSLService) Search(search request.WebsiteSSLSearch) ([]response.W
opts []repo.DBOption
result []response.WebsiteSSLDTO
)
opts = append(opts, commonRepo.WithOrderBy("created_at desc"), websiteSSLRepo.WithByAcmeAccountId(search.AcmeAccountID))
opts = append(opts, commonRepo.WithOrderBy("created_at desc"))
if search.AcmeAccountID != "" {
acmeAccountID, err := strconv.ParseUint(search.AcmeAccountID, 10, 64)
if err != nil {
return nil, err
}
opts = append(opts, websiteSSLRepo.WithByAcmeAccountId(uint(acmeAccountID)))
}
sslList, err := websiteSSLRepo.List(opts...)
if err != nil {
return nil, err
@@ -107,7 +115,7 @@ func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.Webs
if err != nil {
return request.WebsiteSSLCreate{}, err
}
if err := client.UseHTTP(path.Join(constant.AppInstallDir, constant.AppOpenresty, appInstall.Name, "root")); err != nil {
if err := client.UseHTTP(path.Join(appInstall.GetPath(), "root")); err != nil {
return res, err
}
websiteSSL.AutoRenew = create.AutoRenew
+61 -13
View File
@@ -23,12 +23,12 @@ import (
"gorm.io/gorm"
)
func getDomain(domainStr string) (model.WebsiteDomain, error) {
func getDomain(domainStr string, defaultPort int) (model.WebsiteDomain, error) {
domain := model.WebsiteDomain{}
domainArray := strings.Split(domainStr, ":")
if len(domainArray) == 1 {
domain.Domain = domainArray[0]
domain.Port = 80
domain.Port = defaultPort
return domain, nil
}
if len(domainArray) > 1 {
@@ -186,6 +186,7 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
return errors.New("nginx config is not valid")
}
server := servers[0]
server.DeleteListen("80")
var serverNames []string
for _, domain := range domains {
serverNames = append(serverNames, domain.Domain)
@@ -378,27 +379,33 @@ func applySSL(website model.Website, websiteSSL model.WebsiteSSL, req request.We
}
config := nginxFull.SiteConfig.Config
server := config.FindServers()[0]
server.UpdateListen("443", website.DefaultServer, "ssl", "http2")
httpPort := strconv.Itoa(nginxFull.Install.HttpPort)
httpsPort := strconv.Itoa(nginxFull.Install.HttpsPort)
httpPortIPV6 := "[::]:" + httpPort
httpsPortIPV6 := "[::]:" + httpsPort
server.UpdateListen(httpsPort, website.DefaultServer, "ssl", "http2")
if website.IPV6 {
server.UpdateListen("[::]:443", website.DefaultServer, "ssl", "http2")
server.UpdateListen(httpsPortIPV6, website.DefaultServer, "ssl", "http2")
}
switch req.HttpConfig {
case constant.HTTPSOnly:
server.RemoveListenByBind("80")
server.RemoveListenByBind("[::]:80")
server.RemoveListenByBind(httpPort)
server.RemoveListenByBind(httpPortIPV6)
server.RemoveDirective("if", []string{"($scheme"})
case constant.HTTPToHTTPS:
server.UpdateListen("80", website.DefaultServer)
server.UpdateListen(httpPort, website.DefaultServer)
if website.IPV6 {
server.UpdateListen("[::]:80", website.DefaultServer)
server.UpdateListen(httpPortIPV6, website.DefaultServer)
}
server.AddHTTP2HTTPS()
case constant.HTTPAlso:
server.UpdateListen("80", website.DefaultServer)
server.UpdateListen(httpPort, website.DefaultServer)
server.RemoveDirective("if", []string{"($scheme"})
if website.IPV6 {
server.UpdateListen("[::]:80", website.DefaultServer)
server.UpdateListen(httpPortIPV6, website.DefaultServer)
}
}
@@ -536,6 +543,10 @@ func opWebsite(website *model.Website, operate string) error {
if files.NewFileOp().Stat(absoluteRewritePath) {
server.UpdateDirective("include", []string{rewriteInclude})
}
rootIndex := path.Join("/www/sites", website.Alias, "index")
if website.SiteDir != "/" {
rootIndex = path.Join(rootIndex, website.SiteDir)
}
switch website.Type {
case constant.Deployment:
server.RemoveDirective("root", nil)
@@ -546,12 +557,11 @@ func opWebsite(website *model.Website, operate string) error {
proxy := fmt.Sprintf("http://127.0.0.1:%d", appInstall.HttpPort)
server.UpdateRootProxy([]string{proxy})
case constant.Static:
server.UpdateRoot(path.Join("/www/sites", website.Alias, "index"))
server.UpdateRoot(rootIndex)
server.UpdateRootLocation()
case constant.Proxy:
server.RemoveDirective("root", nil)
case constant.Runtime:
rootIndex := path.Join("/www/sites", website.Alias, "index")
server.UpdateRoot(rootIndex)
localPath := ""
if website.ProxyType == constant.RuntimeProxyUnix {
@@ -573,6 +583,44 @@ func opWebsite(website *model.Website, operate string) error {
return nginxCheckAndReload(nginxInstall.SiteConfig.OldContent, config.FilePath, nginxInstall.Install.ContainerName)
}
func changeIPV6(website model.Website, enable bool) error {
nginxFull, err := getNginxFull(&website)
if err != nil {
return nil
}
config := nginxFull.SiteConfig.Config
server := config.FindServers()[0]
listens := server.Listens
if enable {
for _, listen := range listens {
if strings.HasPrefix(listen.Bind, "[::]:") {
continue
}
exist := false
ipv6Bind := fmt.Sprintf("[::]:%s", listen.Bind)
for _, li := range listens {
if li.Bind == ipv6Bind {
exist = true
break
}
}
if !exist {
server.UpdateListen(ipv6Bind, false, listen.GetParameters()[1:]...)
}
}
} else {
for _, listen := range listens {
if strings.HasPrefix(listen.Bind, "[::]:") {
server.RemoveListenByBind(listen.Bind)
}
}
}
if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
return err
}
return nginxCheckAndReload(nginxFull.SiteConfig.OldContent, config.FilePath, nginxFull.Install.ContainerName)
}
func checkIsLinkApp(website model.Website) bool {
if website.Type == constant.Deployment {
return true
@@ -585,7 +633,7 @@ func checkIsLinkApp(website model.Website) bool {
}
func chownRootDir(path string) error {
_, err := cmd.ExecWithTimeOut(fmt.Sprintf("chown -R 1000:1000 %s", path), 1*time.Second)
_, err := cmd.ExecWithTimeOut(fmt.Sprintf(`chown -R 1000:1000 "%s"`, path), 1*time.Second)
if err != nil {
return err
}
+15
View File
@@ -61,3 +61,18 @@ func WithMap(Key string, maps map[string]interface{}, err error) BusinessError {
Err: err,
}
}
func WithNameAndErr(Key string, name string, err error) BusinessError {
paramMap := map[string]interface{}{}
if name != "" {
paramMap["name"] = name
}
if err != nil {
paramMap["err"] = err.Error()
}
return BusinessError{
Msg: Key,
Map: paramMap,
Err: err,
}
}
+2
View File
@@ -12,6 +12,7 @@ const (
Upgrading = "Upgrading"
UpgradeErr = "UpgradeErr"
PullErr = "PullErr"
Rebuilding = "Rebuilding"
ContainerPrefix = "1Panel-"
@@ -20,6 +21,7 @@ const (
AppOpenresty = "openresty"
AppMysql = "mysql"
AppMariaDB = "mariadb"
AppRedis = "redis"
AppResourceLocal = "local"
+6 -1
View File
@@ -42,6 +42,7 @@ var (
ErrTypePasswordExpired = "ErrPasswordExpired"
ErrNameIsExist = "ErrNameIsExist"
ErrDemoEnvironment = "ErrDemoEnvironment"
ErrCmdIllegal = "ErrCmdIllegal"
)
// app
@@ -68,6 +69,8 @@ var (
ErrGroupIsUsed = "ErrGroupIsUsed"
ErrUsernameIsExist = "ErrUsernameIsExist"
ErrUsernameIsNotExist = "ErrUsernameIsNotExist"
ErrBackupMatch = "ErrBackupMatch"
ErrBackupExist = "ErrBackupExist"
)
// ssl
@@ -93,6 +96,8 @@ var (
ErrUserIsExist = "ErrUserIsExist"
ErrDatabaseIsExist = "ErrDatabaseIsExist"
ErrExecTimeOut = "ErrExecTimeOut"
ErrRemoteExist = "ErrRemoteExist"
ErrLocalExist = "ErrLocalExist"
)
// redis
@@ -105,7 +110,6 @@ var (
ErrInUsed = "ErrInUsed"
ErrObjectInUsed = "ErrObjectInUsed"
ErrPortRules = "ErrPortRules"
ErrRepoConn = "ErrRepoConn"
)
// runtime
@@ -120,4 +124,5 @@ var (
var (
ErrBackupInUsed = "ErrBackupInUsed"
ErrOSSConn = "ErrOSSConn"
ErrEntrance = "ErrEntrance"
)
+8
View File
@@ -0,0 +1,8 @@
package constant
const (
Supervisord = "supervisord"
Supervisor = "supervisor"
SupervisorConfigPath = "SupervisorConfigPath"
SupervisorServiceName = "SupervisorServiceName"
)
+1
View File
@@ -2,6 +2,7 @@ package constant
const (
StatusRunning = "Running"
StatusDone = "Done"
StatusStoped = "Stoped"
StatusWaiting = "Waiting"
StatusSuccess = "Success"

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