2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 13:47:15 +08:00

feat: pure-ftpd plugin

This commit is contained in:
耗子
2023-08-03 23:33:31 +08:00
parent 8234a16f9e
commit e731c025b5
17 changed files with 1145 additions and 13 deletions

8
.gitignore vendored
View File

@@ -1,8 +1,6 @@
tmp
.env
.history
.air.toml
panel.conf
/.air.toml
/panel.conf
# Golang #
# `go test -c` 生成的二进制文件
@@ -23,7 +21,7 @@ _cgo_export.*
*.exe
*.o
*.so
panel
/panel
# 压缩包 #
# Git 自带压缩,如果这些压缩包里有代码,建议解压后 commit

View File

@@ -378,7 +378,26 @@ func (c *Mysql57Controller) DatabaseList(ctx http.Context) {
return
}
controllers.Success(ctx, databases)
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(databases) {
controllers.Success(ctx, http.Json{
"total": 0,
"items": []database{},
})
return
}
if endIndex > len(databases) {
endIndex = len(databases)
}
pagedDatabases := databases[startIndex:endIndex]
controllers.Success(ctx, http.Json{
"total": len(databases),
"items": pagedDatabases,
})
}
// AddDatabase 添加数据库
@@ -626,7 +645,26 @@ func (c *Mysql57Controller) UserList(ctx http.Context) {
return
}
controllers.Success(ctx, userGrants)
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(userGrants) {
controllers.Success(ctx, http.Json{
"total": 0,
"items": []user{},
})
return
}
if endIndex > len(userGrants) {
endIndex = len(userGrants)
}
pagedUserGrants := userGrants[startIndex:endIndex]
controllers.Success(ctx, http.Json{
"total": len(userGrants),
"items": pagedUserGrants,
})
}
// AddUser 添加用户

View File

@@ -378,7 +378,26 @@ func (c *Mysql80Controller) DatabaseList(ctx http.Context) {
return
}
controllers.Success(ctx, databases)
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(databases) {
controllers.Success(ctx, http.Json{
"total": 0,
"items": []database{},
})
return
}
if endIndex > len(databases) {
endIndex = len(databases)
}
pagedDatabases := databases[startIndex:endIndex]
controllers.Success(ctx, http.Json{
"total": len(databases),
"items": pagedDatabases,
})
}
// AddDatabase 添加数据库
@@ -626,7 +645,26 @@ func (c *Mysql80Controller) UserList(ctx http.Context) {
return
}
controllers.Success(ctx, userGrants)
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(userGrants) {
controllers.Success(ctx, http.Json{
"total": 0,
"items": []user{},
})
return
}
if endIndex > len(userGrants) {
endIndex = len(userGrants)
}
pagedUserGrants := userGrants[startIndex:endIndex]
controllers.Success(ctx, http.Json{
"total": len(userGrants),
"items": pagedUserGrants,
})
}
// AddUser 添加用户

View File

@@ -0,0 +1,313 @@
package pureftpd
import (
"regexp"
"strings"
"github.com/goravel/framework/contracts/http"
"panel/app/http/controllers"
"panel/pkg/tools"
)
type PureFtpdController struct {
}
type User struct {
Username string `json:"username"`
Path string `json:"path"`
}
func NewPureFtpdController() *PureFtpdController {
return &PureFtpdController{}
}
// Status 获取运行状态
func (c *PureFtpdController) Status(ctx http.Context) {
if !controllers.Check(ctx, "pureftpd") {
return
}
status := tools.ExecShell("systemctl status pure-ftpd | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd状态失败")
return
}
if status == "active" {
controllers.Success(ctx, true)
} else {
controllers.Success(ctx, false)
}
}
// Reload 重载配置
func (c *PureFtpdController) Reload(ctx http.Context) {
if !controllers.Check(ctx, "pureftpd") {
return
}
tools.ExecShell("systemctl reload pure-ftpd")
status := tools.ExecShell("systemctl status pure-ftpd | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd状态失败")
return
}
if status == "active" {
controllers.Success(ctx, true)
} else {
controllers.Success(ctx, false)
}
}
// Restart 重启服务
func (c *PureFtpdController) Restart(ctx http.Context) {
if !controllers.Check(ctx, "pureftpd") {
return
}
tools.ExecShell("systemctl restart pure-ftpd")
status := tools.ExecShell("systemctl status pure-ftpd | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd状态失败")
return
}
if status == "active" {
controllers.Success(ctx, true)
} else {
controllers.Success(ctx, false)
}
}
// Start 启动服务
func (c *PureFtpdController) Start(ctx http.Context) {
if !controllers.Check(ctx, "pureftpd") {
return
}
tools.ExecShell("systemctl start pure-ftpd")
status := tools.ExecShell("systemctl status pure-ftpd | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd状态失败")
return
}
if status == "active" {
controllers.Success(ctx, true)
} else {
controllers.Success(ctx, false)
}
}
// Stop 停止服务
func (c *PureFtpdController) Stop(ctx http.Context) {
if !controllers.Check(ctx, "pureftpd") {
return
}
tools.ExecShell("systemctl stop pure-ftpd")
status := tools.ExecShell("systemctl status pure-ftpd | grep Active | grep -v grep | awk '{print $2}'")
if len(status) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd状态失败")
return
}
if status != "active" {
controllers.Success(ctx, true)
} else {
controllers.Success(ctx, false)
}
}
// List 获取用户列表
func (c *PureFtpdController) List(ctx http.Context) {
if !controllers.Check(ctx, "pureftpd") {
return
}
listRaw := tools.ExecShell("pure-pw list")
if len(listRaw) == 0 {
controllers.Success(ctx, http.Json{
"total": 0,
"items": []User{},
})
return
}
listArr := strings.Split(listRaw, "\n")
var users []User
for _, v := range listArr {
if len(v) == 0 {
continue
}
match := regexp.MustCompile(`(\S+)\s+(\S+)`).FindStringSubmatch(v)
users = append(users, User{
Username: match[1],
Path: strings.Replace(match[2], "/./", "/", 1),
})
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(users) {
controllers.Success(ctx, http.Json{
"total": 0,
"items": []User{},
})
return
}
if endIndex > len(users) {
endIndex = len(users)
}
pagedUsers := users[startIndex:endIndex]
controllers.Success(ctx, http.Json{
"total": len(users),
"items": pagedUsers,
})
}
// Add 添加用户
func (c *PureFtpdController) Add(ctx http.Context) {
if !controllers.Check(ctx, "pureftpd") {
return
}
validator, err := ctx.Request().Validate(map[string]string{
"username": "required",
"password": "required|min_len:6",
"path": "required",
})
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, err.Error())
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
username := ctx.Request().Input("username")
password := ctx.Request().Input("password")
path := ctx.Request().Input("path")
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
if !tools.Exists(path) {
controllers.Error(ctx, http.StatusBadRequest, "目录不存在")
return
}
tools.Chmod(path, 755)
tools.Chown(path, "www", "www")
tools.ExecShell(`yes '` + password + `' | pure-pw useradd ` + username + ` -u www -g www -d ` + path)
tools.ExecShell("pure-pw mkdb")
controllers.Success(ctx, nil)
}
// Delete 删除用户
func (c *PureFtpdController) Delete(ctx http.Context) {
if !controllers.Check(ctx, "pureftpd") {
return
}
validator, err := ctx.Request().Validate(map[string]string{
"username": "required",
})
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, err.Error())
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
username := ctx.Request().Input("username")
tools.ExecShell("pure-pw userdel " + username + " -m")
tools.ExecShell("pure-pw mkdb")
controllers.Success(ctx, nil)
}
// ChangePassword 修改密码
func (c *PureFtpdController) ChangePassword(ctx http.Context) {
if !controllers.Check(ctx, "pureftpd") {
return
}
validator, err := ctx.Request().Validate(map[string]string{
"username": "required",
"password": "required|min_len:6",
})
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, err.Error())
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
username := ctx.Request().Input("username")
password := ctx.Request().Input("password")
tools.ExecShell(`yes '` + password + `' | pure-pw passwd ` + username + ` -m`)
tools.ExecShell("pure-pw mkdb")
controllers.Success(ctx, nil)
}
// GetPort 获取端口
func (c *PureFtpdController) GetPort(ctx http.Context) {
if !controllers.Check(ctx, "pureftpd") {
return
}
port := tools.ExecShell(`cat /www/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`)
if len(port) == 0 {
controllers.Error(ctx, http.StatusInternalServerError, "获取PureFtpd端口失败")
return
}
controllers.Success(ctx, port)
}
// SetPort 设置端口
func (c *PureFtpdController) SetPort(ctx http.Context) {
if !controllers.Check(ctx, "pureftpd") {
return
}
validator, err := ctx.Request().Validate(map[string]string{
"port": "required",
})
if err != nil {
controllers.Error(ctx, http.StatusBadRequest, err.Error())
return
}
if validator.Fails() {
controllers.Error(ctx, http.StatusBadRequest, validator.Errors().One())
return
}
port := ctx.Request().Input("port")
tools.ExecShell(`sed -i "s/Bind.*/Bind 0.0.0.0,` + port + `/g" /www/server/pure-ftpd/etc/pure-ftpd.conf`)
if tools.IsRHEL() {
tools.ExecShell("firewall-cmd --zone=public --add-port=" + port + "/tcp --permanent")
tools.ExecShell("firewall-cmd --reload")
} else {
tools.ExecShell("ufw allow " + port + "/tcp")
tools.ExecShell("ufw reload")
}
tools.ExecShell("systemctl restart pure-ftpd")
controllers.Success(ctx, nil)
}

View File

@@ -48,6 +48,8 @@ func (r *SafeController) GetFirewallRules(ctx http.Context) {
Success(ctx, nil)
return
}
page := ctx.Request().QueryInt("page", 1)
limit := ctx.Request().QueryInt("limit", 10)
if tools.IsRHEL() {
out := tools.ExecShell("firewall-cmd --list-all 2>&1")
@@ -66,7 +68,24 @@ func (r *SafeController) GetFirewallRules(ctx http.Context) {
})
}
Success(ctx, rules)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(rules) {
Success(ctx, http.Json{
"total": 0,
"items": []map[string]string{},
})
return
}
if endIndex > len(rules) {
endIndex = len(rules)
}
pagedRules := rules[startIndex:endIndex]
Success(ctx, http.Json{
"total": len(rules),
"items": pagedRules,
})
} else {
out := tools.ExecShell("ufw status | grep -v '(v6)' | grep ALLOW | awk '{print $1}'")
if len(out) == 0 {
@@ -82,7 +101,24 @@ func (r *SafeController) GetFirewallRules(ctx http.Context) {
})
}
Success(ctx, rules)
startIndex := (page - 1) * limit
endIndex := page * limit
if startIndex > len(rules) {
Success(ctx, http.Json{
"total": 0,
"items": []map[string]string{},
})
return
}
if endIndex > len(rules) {
endIndex = len(rules)
}
pagedRules := rules[startIndex:endIndex]
Success(ctx, http.Json{
"total": len(rules),
"items": pagedRules,
})
}
}

View File

@@ -0,0 +1,13 @@
package pureftpd
var (
Name = "Pure-FTPd"
Description = "Pure-Ftpd 是一个快速、高效、轻便、安全的 FTP 服务器它以安全和配置简单为设计目标支持虚拟主机IPV6PAM 等功能。"
Slug = "pureftpd"
Version = "1.0.50"
Requires = []string{}
Excludes = []string{}
Install = `bash /www/panel/scripts/pureftpd/install.sh`
Uninstall = `bash /www/panel/scripts/pureftpd/uninstall.sh`
Update = `bash /www/panel/scripts/pureftpd/update.sh`
)

View File

@@ -14,6 +14,7 @@ import (
"panel/app/plugins/php81"
"panel/app/plugins/php82"
"panel/app/plugins/phpmyadmin"
"panel/app/plugins/pureftpd"
"panel/app/plugins/s3fs"
"panel/app/plugins/supervisor"
)
@@ -147,6 +148,17 @@ func (r *PluginImpl) All() []PanelPlugin {
Uninstall: phpmyadmin.Uninstall,
Update: phpmyadmin.Update,
})
p = append(p, PanelPlugin{
Name: pureftpd.Name,
Description: pureftpd.Description,
Slug: pureftpd.Slug,
Version: pureftpd.Version,
Requires: pureftpd.Requires,
Excludes: pureftpd.Excludes,
Install: pureftpd.Install,
Uninstall: pureftpd.Uninstall,
Update: pureftpd.Update,
})
p = append(p, PanelPlugin{
Name: s3fs.Name,
Description: s3fs.Description,

View File

@@ -51,7 +51,7 @@ func init() {
// Here you may specify the default timezone for your application.
// Example: UTC, Asia/Shanghai
// More: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
"timezone": carbon.UTC,
"timezone": carbon.PRC,
// Encryption Key
//

View File

@@ -203,6 +203,15 @@ Date: 2022-07-22
{field: 'name', title: '库名', fixed: 'left', unresize: true, sort: true}
, {fixed: 'right', title: '操作', toolbar: '#mysql-database-list-control', width: 150}
]]
, parseData: function (res) {
return {
"code": res.code,
"msg": res.message,
"count": res.data.total,
"data": res.data.items
};
}
, page: true
});
// 头工具栏事件
table.on('toolbar(mysql-database-list)', function (obj) {
@@ -272,6 +281,15 @@ Date: 2022-07-22
, {field: 'grants', title: '权限'}
, {fixed: 'right', title: '操作', toolbar: '#mysql-user-list-control', width: 150}
]]
, parseData: function (res) {
return {
"code": res.code,
"msg": res.message,
"count": res.data.total,
"data": res.data.items
};
}
, page: true
});
// 头工具栏事件
table.on('toolbar(mysql-user-list)', function (obj) {

View File

@@ -203,6 +203,15 @@ Date: 2022-07-22
{field: 'name', title: '库名', fixed: 'left', unresize: true, sort: true}
, {fixed: 'right', title: '操作', toolbar: '#mysql-database-list-control', width: 150}
]]
, parseData: function (res) {
return {
"code": res.code,
"msg": res.message,
"count": res.data.total,
"data": res.data.items
};
}
, page: true
});
// 头工具栏事件
table.on('toolbar(mysql-database-list)', function (obj) {
@@ -272,6 +281,15 @@ Date: 2022-07-22
, {field: 'grants', title: '权限'}
, {fixed: 'right', title: '操作', toolbar: '#mysql-user-list-control', width: 150}
]]
, parseData: function (res) {
return {
"code": res.code,
"msg": res.message,
"count": res.data.total,
"data": res.data.items
};
}
, page: true
});
// 头工具栏事件
table.on('toolbar(mysql-user-list)', function (obj) {

View File

@@ -0,0 +1,305 @@
<!--
Name: Pure-Ftpd管理器
Author: 耗子
Date: 2023-08-03
-->
<title>Pure-Ftpd</title>
<div class="layui-fluid" id="component-tabs">
<div class="layui-row">
<div class="layui-col-md12">
<div class="layui-card">
<div class="layui-card-header">Pure-Ftpd 运行状态</div>
<div class="layui-card-body">
<blockquote id="pureftpd-status" class="layui-elem-quote layui-quote-nm">当前状态:<span
class="layui-badge layui-bg-black">获取中</span></blockquote>
<div class="layui-btn-container" style="padding-top: 30px;">
<button id="pureftpd-start" class="layui-btn">启动</button>
<button id="pureftpd-stop" class="layui-btn layui-btn-danger">停止</button>
<button id="pureftpd-restart" class="layui-btn layui-btn-warm">重启</button>
<button id="pureftpd-reload" class="layui-btn layui-btn-normal">重载</button>
</div>
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">
<legend>基本设置</legend>
</fieldset>
<div class="layui-form" lay-filter="pureftpd_setting">
<div class="layui-form-item">
<label class="layui-form-label" style="font-size: 13px;">端口</label>
<div class="layui-input-inline">
<input type="text" name="pureftpd_port" value="获取中ing..." class="layui-input"
disabled>
</div>
<div class="layui-form-mid layui-word-aux">设置Pure-Ftpd的访问端口</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn layui-btn-sm" lay-submit lay-filter="pureftpd_setting_submit">
确认修改
</button>
</div>
</div>
</div>
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">Pure-Ftpd 用户列表</div>
<div class="layui-card-body">
<table class="layui-hide" id="pureftpd-user-list" lay-filter="pureftpd-user-list"></table>
<!-- 顶部工具栏 -->
<script type="text/html" id="pureftpd-user-list-bar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="add_user">新建用户</button>
</div>
</script>
<!-- 右侧管理 -->
<script type="text/html" id="pureftpd-user-list-control">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="change_password">改密</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
</div>
</div>
</div>
</div>
</div>
<script>
layui.use(['index', 'form', 'table', 'view'], function () {
let $ = layui.$
, admin = layui.admin
, table = layui.table
, form = layui.form
, view = layui.view;
// 获取运行状态并渲染
admin.req({
url: "/api/plugins/pureftpd/status"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
return false;
}
if (result.data) {
$('#pureftpd-status').html('当前状态:<span class="layui-badge layui-bg-green">运行中</span>');
} else {
$('#pureftpd-status').html('当前状态:<span class="layui-badge layui-bg-red">已停止</span>');
}
}
});
// 获取端口并渲染
admin.req({
url: "/api/plugins/pureftpd/port"
, type: 'get'
, success: function (result) {
if (result.code !== 0) {
return false;
}
$('input[name=pureftpd_port]').val(result.data);
$('input').attr('disabled', false);
form.render();
}
});
// 监听提交
form.on('submit(pureftpd_setting_submit)', function (data) {
data.field.port = $('input[name=pureftpd_port]').val();
index = layer.msg('请稍候...', {icon: 16, time: 0, shade: 0.3});
admin.req({
url: "/api/plugins/pureftpd/port"
, type: 'post'
, data: data.field
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
layer.msg('设置成功', {icon: 1});
}
});
return false;
});
// 获取用户列表
table.render({
elem: '#pureftpd-user-list'
, url: '/api/plugins/pureftpd/list'
, toolbar: '#pureftpd-user-list-bar'
, title: 'Pure-Ftpd 用户列表'
, cols: [[
{field: 'username', title: '用户名', fixed: 'left', unresize: true, sort: true}
, {field: 'path', title: '目录', unresize: true, sort: true}
, {fixed: 'right', title: '操作', toolbar: '#pureftpd-user-list-control', width: 150}
]]
, parseData: function (res) {
return {
"code": res.code,
"msg": res.message,
"count": res.data.total,
"data": res.data.items
};
}
, page: true
});
// 头工具栏事件
table.on('toolbar(pureftpd-user-list)', function (obj) {
if (obj.event === 'add_user') {
admin.popup({
title: '新建Pure-Ftpd用户'
, area: ['600px', '400px']
, id: 'LAY-popup-pureftpd-user-add'
, success: function (layer, index) {
view(this.id).render('plugins/pureftpd/add_user', {}).done(function () {
form.render(null, 'LAY-popup-pureftpd-user-add');
});
}
});
}
});
// 行工具事件
table.on('tool(pureftpd-user-list)', function (obj) {
let data = obj.data;
if (obj.event === 'del') {
layer.confirm('确定要删除用户 <b style="color: red;">' + data.username + '</b> 吗?', function (index) {
index = layer.msg('请稍等...', {
icon: 16
, time: 0
, shade: 0.3
});
admin.req({
url: "/api/plugins/pureftpd/delete"
, type: 'post'
, data: data
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
layer.msg('Pure-Ftpd用户删除失败请刷新重试')
return false;
}
obj.del();
layer.alert(data.username + '删除成功!');
}
});
layer.close(index);
});
} else if (obj.event === 'change_password') {
// 弹出输入密码框
layer.prompt({
formType: 1
, title: '请输入新密码6位以上'
}, function (value, index) {
layer.close(index);
index = layer.msg('请稍等...', {
icon: 16
, time: 0
, shade: 0.3
});
// 发送请求
admin.req({
url: "/api/plugins/pureftpd/changePassword"
, type: 'post'
, data: {
username: data.username,
password: value
}
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
layer.msg('密码修改失败,请刷新重试!')
return false;
}
layer.alert('用户' + data.username + '密码修改成功!');
}
});
});
}
});
// 事件监听
$('#pureftpd-start').click(function () {
layer.confirm('确定要启动Pure-Ftpd吗', {
btn: ['启动', '取消']
}, function () {
index = layer.msg('请稍等...', {
icon: 16
, time: 0
, shade: 0.3
});
admin.req({
url: "/api/plugins/pureftpd/start"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
admin.events.refresh();
layer.alert('Pure-Ftpd启动成功');
}
});
});
});
$('#pureftpd-stop').click(function () {
layer.confirm('停止Pure-Ftpd将导致FTP无法连接是否继续停止', {
btn: ['停止', '取消']
}, function () {
index = layer.msg('请稍等...', {
icon: 16
, time: 0
, shade: 0.3
});
admin.req({
url: "/api/plugins/pureftpd/stop"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
admin.events.refresh();
layer.alert('Pure-Ftpd停止成功');
}
});
});
});
$('#pureftpd-restart').click(function () {
layer.confirm('确定要重启Pure-Ftpd吗', {
btn: ['重启', '取消']
}, function () {
index = layer.msg('请稍等...', {
icon: 16
, time: 0
, shade: 0.3
});
admin.req({
url: "/api/plugins/pureftpd/restart"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
admin.events.refresh();
layer.alert('Pure-Ftpd重启成功');
}
});
});
});
$('#pureftpd-reload').click(function () {
index = layer.msg('请稍等...', {
icon: 16
, time: 0
, shade: 0.3
});
admin.req({
url: "/api/plugins/pureftpd/reload"
, type: 'post'
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
return false;
}
layer.alert('Pure-Ftpd重载成功');
}
});
});
});
</script>

View File

@@ -0,0 +1,80 @@
<!--
Name: Pure-Ftpd管理器 - 新建用户
Author: 耗子
Date: 2023-08-03
-->
<script type="text/html" template lay-done="layui.data.sendParams(d.params)">
<form class="layui-form" action="" lay-filter="add-pureftpd-user-form">
<div class="layui-form-item">
<label class="layui-form-label">用户名</label>
<div class="layui-input-block">
<input type="text" name="username" id="add-pureftpd-user-username"
lay-verify="required" placeholder="请输入用户名" class="layui-input"
value="">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">密码</label>
<div class="layui-input-block">
<input type="password" name="password" id="add-pureftpd-user-password"
lay-verify="required" placeholder="请输入密码" class="layui-input"
value="">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">目录</label>
<div class="layui-input-block">
<input type="text" name="path" id="add-pureftpd-user-path"
lay-verify="required" placeholder="请输入目录" class="layui-input"
value="">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<div class="layui-footer">
<button class="layui-btn" lay-submit="" lay-filter="add-pureftpd-user-submit">立即提交</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</div>
</form>
</script>
<script>
layui.data.sendParams = function (params) {
layui.use(['admin', 'form', 'form', 'table'], function () {
var admin = layui.admin
, layer = layui.layer
, form = layui.form
, table = layui.table;
form.render();
// 提交
form.on('submit(add-pureftpd-user-submit)', function (data) {
index = layer.msg('正在提交...', {icon: 16, time: 0, shade: 0.3});
admin.req({
url: "/api/plugins/pureftpd/add"
, type: 'post'
, data: data.field
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
layer.msg('Pure-Ftpd用户添加失败请刷新重试')
return false;
}
table.reload('pureftpd-user-list');
layer.alert('Pure-Ftpd用户添加成功', {
icon: 1
, title: '提示'
, btn: ['确定']
, yes: function (index) {
layer.closeAll();
}
});
}
});
return false;
});
});
};
</script>

View File

@@ -1,7 +1,7 @@
<!--
Name: 系统安全
Author: 耗子
Date: 2023-07-21
Date: 2023-08-03
-->
<title>系统安全</title>
@@ -218,6 +218,15 @@ Date: 2023-07-21
, {field: 'protocol', title: '协议', sort: true}
, {fixed: 'right', title: '操作', toolbar: '#safe-port-setting', width: 150}
]]
, parseData: function (res) {
return {
"code": res.code,
"msg": res.message,
"count": res.data.total,
"data": res.data.items
};
}
, page: true
});
table.on('tool(safe-port)', function (obj) {
let data = obj.data;

View File

@@ -13,6 +13,7 @@ import (
"panel/app/http/controllers/plugins/php81"
"panel/app/http/controllers/plugins/php82"
"panel/app/http/controllers/plugins/phpmyadmin"
"panel/app/http/controllers/plugins/pureftpd"
"panel/app/http/controllers/plugins/s3fs"
"panel/app/http/controllers/plugins/supervisor"
"panel/app/http/middleware"
@@ -170,6 +171,20 @@ func Plugin() {
route.Get("info", phpMyAdminController.Info)
route.Post("port", phpMyAdminController.SetPort)
})
facades.Route().Prefix("api/plugins/pureftpd").Middleware(middleware.Jwt()).Group(func(route route.Route) {
pureFtpdController := pureftpd.NewPureFtpdController()
route.Get("status", pureFtpdController.Status)
route.Post("reload", pureFtpdController.Reload)
route.Post("start", pureFtpdController.Start)
route.Post("stop", pureFtpdController.Stop)
route.Post("restart", pureFtpdController.Restart)
route.Get("list", pureFtpdController.List)
route.Post("add", pureFtpdController.Add)
route.Post("delete", pureFtpdController.Delete)
route.Post("changePassword", pureFtpdController.ChangePassword)
route.Get("port", pureFtpdController.GetPort)
route.Post("port", pureFtpdController.SetPort)
})
facades.Route().Prefix("api/plugins/s3fs").Middleware(middleware.Jwt()).Group(func(route route.Route) {
s3fsController := s3fs.NewS3fsController()
route.Get("list", s3fsController.List)

130
scripts/pureftpd/install.sh Normal file
View File

@@ -0,0 +1,130 @@
#!/bin/bash
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
: '
Copyright 2022 HaoZi Technology Co., Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
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.
'
HR="+----------------------------------------------------"
OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown")
downloadUrl="https://dl.cdn.haozi.net/panel/pure-ftpd"
setupPath="/www"
pureftpdPath="${setupPath}/server/pure-ftpd"
pureftpdVersion="1.0.50"
# 准备安装目录
rm -rf ${pureftpdPath}
mkdir -p ${pureftpdPath}
cd ${pureftpdPath}
wget -O ${pureftpdPath}/pure-ftpd-${pureftpdVersion}.tar.gz ${downloadUrl}/pure-ftpd-${pureftpdVersion}.tar.gz
if [ "$?" != "0" ]; then
echo -e $HR
echo "错误Pure-Ftpd-${pureftpdVersion}下载失败,请检查网络是否正常。"
exit 1
fi
tar -xvf pure-ftpd-${pureftpdVersion}.tar.gz
rm -f pure-ftpd-${pureftpdVersion}.tar.gz
mv pure-ftpd-${pureftpdVersion} src
cd src
./configure --prefix=${pureftpdPath} CFLAGS=-O2 --with-puredb --with-quotas --with-cookie --with-virtualhosts --with-diraliases --with-sysquotas --with-ratios --with-altlog --with-paranoidmsg --with-shadow --with-welcomemsg --with-throttling --with-uploadscript --with-language=simplified-chinese --with-rfc2640 --with-ftpwho --with-tls
if [ "$?" != "0" ]; then
echo -e $HR
echo "错误Pure-Ftpd-${pureftpdVersion}编译配置失败,请截图错误信息寻求帮助。"
exit 1
fi
make
if [ "$?" != "0" ]; then
echo -e $HR
echo "错误Pure-Ftpd-${pureftpdVersion}编译失败,请截图错误信息寻求帮助。"
exit 1
fi
make install
if [ ! -f "${pureftpdPath}/bin/pure-pw" ]; then
echo -e $HR
echo "错误Pure-Ftpd-${pureftpdVersion}安装失败,请截图错误信息寻求帮助。"
exit 1
fi
# 修改 pure-ftpd 配置文件
sed -i "s!# PureDB\s*/etc/pureftpd.pdb!PureDB ${pureftpdPath}/etc/pureftpd.pdb!" ${pureftpdPath}/etc/pure-ftpd.conf
sed -i 's!# ChrootEveryone\s*yes!ChrootEveryone yes!' ${pureftpdPath}/etc/pure-ftpd.conf
sed -i 's!NoAnonymous\s*no!NoAnonymous yes!' ${pureftpdPath}/etc/pure-ftpd.conf
sed -i 's!AnonymousCanCreateDirs\s*yes!AnonymousCanCreateDirs no!' ${pureftpdPath}/etc/pure-ftpd.conf
sed -i 's!AnonymousCantUpload\s*yes!AnonymousCantUpload no!' ${pureftpdPath}/etc/pure-ftpd.conf
sed -i 's!PAMAuthentication\s*yes!PAMAuthentication no!' ${pureftpdPath}/etc/pure-ftpd.conf
sed -i 's!UnixAuthentication\s*yes!UnixAuthentication no!' ${pureftpdPath}/etc/pure-ftpd.conf
sed -i 's!# PassivePortRange\s*30000 50000!PassivePortRange 39000 40000!' ${pureftpdPath}/etc/pure-ftpd.conf
sed -i 's!PassivePortRange\s*30000 50000!PassivePortRange 39000 40000!' ${pureftpdPath}/etc/pure-ftpd.conf
sed -i 's!LimitRecursion\s*10000 8!LimitRecursion 20000 8!' ${pureftpdPath}/etc/pure-ftpd.conf
sed -i 's!# TLS!TLS!' ${pureftpdPath}/etc/pure-ftpd.conf
sed -i "s!# CertFile\s*/etc/ssl/private/pure-ftpd.pem!CertFile ${pureftpdPath}/etc/pure-ftpd.pem!" ${pureftpdPath}/etc/pure-ftpd.conf
sed -i 's!# Bind\s*127.0.0.1,21!Bind 0.0.0.0,21!' ${pureftpdPath}/etc/pure-ftpd.conf
touch ${pureftpdPath}/etc/pureftpd.passwd
touch ${pureftpdPath}/etc/pureftpd.pdb
openssl dhparam -out ${pureftpdPath}/etc/pure-ftpd-dhparams.pem 2048
openssl req -x509 -nodes -days 3560 -newkey rsa:2048 -sha256 -keyout ${pureftpdPath}/etc/pure-ftpd.pem -out ${pureftpdPath}/etc/pure-ftpd.pem << EOF
CN
Beijing
Beijing
HaoZi Technology Co., Ltd
HaoZi Panel
github.com/haozi-team/panel
panel@haozi.net
EOF
chmod 600 ${pureftpdPath}/etc/*.pem
# 添加系统服务
ln -sf ${pureftpdPath}/bin/pure-pw /usr/bin/pure-pw
cat > /etc/systemd/system/pure-ftpd.service << EOF
[Unit]
Description=Pure-FTPd FTP server
After=network.target
[Service]
Type=forking
PIDFile=/var/run/pure-ftpd.pid
ExecStart=${pureftpdPath}/sbin/pure-ftpd ${pureftpdPath}/etc/pure-ftpd.conf
ExecReload=/bin/kill -HUP \$MAINPID
ExecStop=/bin/kill -TERM \$MAINPID
[Install]
WantedBy=multi-user.target
EOF
# 添加防火墙规则
if [ "${OS}" == "centos" ]; then
firewall-cmd --zone=public --add-port=21/tcp --permanent
firewall-cmd --zone=public --add-port=39000-40000/tcp --permanent
firewall-cmd --reload
elif [ "${OS}" == "debian" ]; then
ufw allow 21/tcp
ufw allow 39000:40000/tcp
ufw reload
fi
systemctl daemon-reload
systemctl enable pure-ftpd.service
systemctl start pure-ftpd.service
panel writePlugin pureftpd 1.0.50
echo -e "${HR}\nPure-Ftpd-${pureftpdVersion} 安装完成\n${HR}"

View File

@@ -0,0 +1,31 @@
#!/bin/bash
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
: '
Copyright 2022 HaoZi Technology Co., Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
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.
'
HR="+----------------------------------------------------"
OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown")
systemctl stop pure-ftpd
systemctl disable pure-ftpd
rm -f /etc/systemd/system/pure-ftpd.service
systemctl daemon-reload
pkill -9 pure-ftpd
rm -rf /www/server/pure-ftpd
rm -f /usr/bin/pure-pw
echo -e "${HR}\nPure-Ftpd 卸载完成\n${HR}"

View File

@@ -0,0 +1,78 @@
#!/bin/bash
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
: '
Copyright 2022 HaoZi Technology Co., Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
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.
'
HR="+----------------------------------------------------"
OS=$(source /etc/os-release && { [[ "$ID" == "debian" ]] && echo "debian"; } || { [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "rocky" ]] || [[ "$ID" == "almalinux" ]] && echo "centos"; } || echo "unknown")
downloadUrl="https://dl.cdn.haozi.net/panel/pure-ftpd"
setupPath="/www"
pureftpdPath="${setupPath}/server/pure-ftpd"
pureftpdVersion="1.0.50"
# 准备安装目录
cp ${pureftpdPath}/etc/pureftpd.passwd /tmp/pureftpd.passwd
cp ${pureftpdPath}/etc/pureftpd.pdb /tmp/pureftpd.pdb
cp ${pureftpdPath}/etc/pureftpd.conf /tmp/pureftpd.conf
systemctl stop pure-ftpd.service
rm -rf ${pureftpdPath}
mkdir -p ${pureftpdPath}
cd ${pureftpdPath}
wget -O ${pureftpdPath}/pure-ftpd-${pureftpdVersion}.tar.gz ${downloadUrl}/pure-ftpd-${pureftpdVersion}.tar.gz
if [ "$?" != "0" ]; then
echo -e $HR
echo "错误Pure-Ftpd-${pureftpdVersion}下载失败,请检查网络是否正常。"
exit 1
fi
tar -xvf pure-ftpd-${pureftpdVersion}.tar.gz
rm -f pure-ftpd-${pureftpdVersion}.tar.gz
mv pure-ftpd-${pureftpdVersion} src
cd src
./configure --prefix=${pureftpdPath} CFLAGS=-O2 --with-puredb --with-quotas --with-cookie --with-virtualhosts --with-diraliases --with-sysquotas --with-ratios --with-altlog --with-paranoidmsg --with-shadow --with-welcomemsg --with-throttling --with-uploadscript --with-language=simplified-chinese --with-rfc2640 --with-ftpwho --with-tls
if [ "$?" != "0" ]; then
echo -e $HR
echo "错误Pure-Ftpd-${pureftpdVersion}编译配置失败,请截图错误信息寻求帮助。"
exit 1
fi
make
if [ "$?" != "0" ]; then
echo -e $HR
echo "错误Pure-Ftpd-${pureftpdVersion}编译失败,请截图错误信息寻求帮助。"
exit 1
fi
make install
if [ ! -f "${pureftpdPath}/bin/pure-pw" ]; then
echo -e $HR
echo "错误Pure-Ftpd-${pureftpdVersion}安装失败,请截图错误信息寻求帮助。"
exit 1
fi
# 还原配置
cp /tmp/pureftpd.passwd ${pureftpdPath}/etc/pureftpd.passwd
cp /tmp/pureftpd.pdb ${pureftpdPath}/etc/pureftpd.pdb
cp /tmp/pureftpd.conf ${pureftpdPath}/etc/pureftpd.conf
systemctl start pure-ftpd.service
panel writePlugin pureftpd 1.0.50
echo -e "${HR}\nPure-Ftpd-${pureftpdVersion} 升级完成\n${HR}"