mirror of
https://github.com/xkatld/lxdapi-web-server.git
synced 2026-05-13 17:08:17 +08:00
重构:整理项目结构,删除冗余文件并调整目录组织
This commit is contained in:
@@ -163,7 +163,6 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input type="hidden" name="ssl_verify" value="0">
|
||||
<input type="checkbox" name="ssl_verify" value="1" class="form-check-input" id="sslCheck">
|
||||
<label class="form-check-label" for="sslCheck">SSL 证书验证</label>
|
||||
<div class="text-muted small">自签名证书请关闭</div>
|
||||
@@ -171,7 +170,6 @@
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input type="hidden" name="active" value="0">
|
||||
<input type="checkbox" name="active" value="1" class="form-check-input" id="activeCheck" checked>
|
||||
<label class="form-check-label" for="activeCheck">启用服务器</label>
|
||||
</div>
|
||||
@@ -234,7 +232,6 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input type="hidden" name="ssl_verify" value="0">
|
||||
<input type="checkbox" name="ssl_verify" value="1" class="form-check-input" id="editSslCheck">
|
||||
<label class="form-check-label" for="editSslCheck">SSL 证书验证</label>
|
||||
<div class="text-muted small">自签名证书请关闭</div>
|
||||
@@ -242,7 +239,6 @@
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input type="hidden" name="active" value="0">
|
||||
<input type="checkbox" name="active" value="1" class="form-check-input" id="editActiveCheck">
|
||||
<label class="form-check-label" for="editActiveCheck">启用服务器</label>
|
||||
</div>
|
||||
@@ -0,0 +1,34 @@
|
||||
{% apply spaceless %}
|
||||
{% autoescape false %}
|
||||
|
||||
<p>尊敬的 {{ client.first_name }},</p>
|
||||
|
||||
<p>您的容器已激活,可以开始使用了。</p>
|
||||
|
||||
<h3>容器信息</h3>
|
||||
|
||||
<table style="border-collapse: collapse; width: 100%; max-width: 500px;">
|
||||
<tr>
|
||||
<td style="padding: 8px; border: 1px solid #ddd;"><strong>容器名称</strong></td>
|
||||
<td style="padding: 8px; border: 1px solid #ddd;">{{ service.container_name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px; border: 1px solid #ddd;"><strong>服务器</strong></td>
|
||||
<td style="padding: 8px; border: 1px solid #ddd;">{{ service.server }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px; border: 1px solid #ddd;"><strong>用户名</strong></td>
|
||||
<td style="padding: 8px; border: 1px solid #ddd;">root</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px; border: 1px solid #ddd;"><strong>密码</strong></td>
|
||||
<td style="padding: 8px; border: 1px solid #ddd;">{{ service.password }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="margin-top: 20px;">您可以在用户中心管理您的容器。</p>
|
||||
|
||||
<p>感谢您的使用!</p>
|
||||
|
||||
{% endautoescape %}
|
||||
{% endapply %}
|
||||
@@ -0,0 +1,18 @@
|
||||
{% apply spaceless %}
|
||||
{% autoescape false %}
|
||||
|
||||
<p>尊敬的 {{ client.first_name }},</p>
|
||||
|
||||
<p>您的容器服务已取消。</p>
|
||||
|
||||
<table style="border-collapse: collapse; width: 100%; max-width: 500px;">
|
||||
<tr>
|
||||
<td style="padding: 8px; border: 1px solid #ddd;"><strong>容器名称</strong></td>
|
||||
<td style="padding: 8px; border: 1px solid #ddd;">{{ service.container_name }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="margin-top: 20px;">如有疑问,请联系客服。</p>
|
||||
|
||||
{% endautoescape %}
|
||||
{% endapply %}
|
||||
@@ -0,0 +1,22 @@
|
||||
{% apply spaceless %}
|
||||
{% autoescape false %}
|
||||
|
||||
<p>尊敬的 {{ client.first_name }},</p>
|
||||
|
||||
<p>您的容器已被暂停。</p>
|
||||
|
||||
<table style="border-collapse: collapse; width: 100%; max-width: 500px;">
|
||||
<tr>
|
||||
<td style="padding: 8px; border: 1px solid #ddd;"><strong>容器名称</strong></td>
|
||||
<td style="padding: 8px; border: 1px solid #ddd;">{{ service.container_name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px; border: 1px solid #ddd;"><strong>原因</strong></td>
|
||||
<td style="padding: 8px; border: 1px solid #ddd;">{{ order.reason|default('费用逾期') }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="margin-top: 20px;">如需恢复服务,请联系我们。</p>
|
||||
|
||||
{% endautoescape %}
|
||||
{% endapply %}
|
||||
@@ -0,0 +1,20 @@
|
||||
{% apply spaceless %}
|
||||
{% autoescape false %}
|
||||
|
||||
<p>尊敬的 {{ client.first_name }},</p>
|
||||
|
||||
<p>您的容器已恢复,现在可以正常使用了。</p>
|
||||
|
||||
<table style="border-collapse: collapse; width: 100%; max-width: 500px;">
|
||||
<tr>
|
||||
<td style="padding: 8px; border: 1px solid #ddd;"><strong>容器名称</strong></td>
|
||||
<td style="padding: 8px; border: 1px solid #ddd;">{{ service.container_name }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="margin-top: 20px;">您可以在用户中心管理您的容器。</p>
|
||||
|
||||
<p>感谢您的续费!</p>
|
||||
|
||||
{% endautoescape %}
|
||||
{% endapply %}
|
||||
@@ -5,5 +5,5 @@
|
||||
"description": "LXDAPI 容器管理插件",
|
||||
"author": "xkatld",
|
||||
"homepage_url": "https://github.com/xkatld/lxdapi-web-server",
|
||||
"version": "v2.0.2"
|
||||
"version": "v2.0.3"
|
||||
}
|
||||
99
Fmis/swapidc/swapidc_lxdapi/includes/lxd_api.php
Normal file
99
Fmis/swapidc/swapidc_lxdapi/includes/lxd_api.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
/**
|
||||
* LXD API 封装类 for SWAPIDC
|
||||
*/
|
||||
|
||||
class LXD_API {
|
||||
private $serverip;
|
||||
private $serverport;
|
||||
private $apikey;
|
||||
private $protocol = 'https';
|
||||
private $timeout = 30;
|
||||
|
||||
public function __construct($data) {
|
||||
$this->serverip = $data['serverip'] ?? '';
|
||||
$this->serverport = $data['serverport'] ?? '8443';
|
||||
$this->apikey = $data['serveraccesshash'] ?? '';
|
||||
}
|
||||
|
||||
public function get($endpoint, $params = []) {
|
||||
return $this->request('GET', $endpoint, $params);
|
||||
}
|
||||
|
||||
public function post($endpoint, $params = []) {
|
||||
return $this->request('POST', $endpoint, $params);
|
||||
}
|
||||
|
||||
public function delete($endpoint, $params = []) {
|
||||
return $this->request('DELETE', $endpoint, $params);
|
||||
}
|
||||
|
||||
private function request($method, $endpoint, $params = []) {
|
||||
if (empty($this->serverip)) {
|
||||
throw new Exception("服务器地址未配置");
|
||||
}
|
||||
|
||||
$url = $this->protocol . '://' . $this->serverip . ':' . $this->serverport . $endpoint;
|
||||
|
||||
if ($method === 'GET' && !empty($params) && strpos($endpoint, '?') === false) {
|
||||
$url .= '?' . http_build_query($params);
|
||||
}
|
||||
|
||||
$ch = curl_init();
|
||||
|
||||
$headers = [
|
||||
'X-API-Hash: ' . $this->apikey,
|
||||
'Content-Type: application/json',
|
||||
];
|
||||
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => $this->timeout,
|
||||
CURLOPT_CONNECTTIMEOUT => 10,
|
||||
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||
CURLOPT_CUSTOMREQUEST => $method,
|
||||
CURLOPT_HTTPHEADER => $headers,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_SSL_VERIFYHOST => false,
|
||||
CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2,
|
||||
]);
|
||||
|
||||
if (($method === 'POST' || $method === 'DELETE') && !empty($params)) {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));
|
||||
}
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
$curlErrno = curl_errno($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curlErrno) {
|
||||
throw new Exception("连接失败: {$curlError}");
|
||||
}
|
||||
|
||||
if (empty($response)) {
|
||||
throw new Exception("服务器返回空响应 (HTTP {$httpCode})");
|
||||
}
|
||||
|
||||
$decoded = json_decode($response, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new Exception("响应解析失败: " . json_last_error_msg());
|
||||
}
|
||||
|
||||
if ($httpCode >= 200 && $httpCode < 300) {
|
||||
return [
|
||||
'success' => isset($decoded['code']) ? $decoded['code'] == 200 : true,
|
||||
'message' => $decoded['msg'] ?? $decoded['message'] ?? '',
|
||||
'data' => $decoded['data'] ?? $decoded,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => $decoded['msg'] ?? $decoded['message'] ?? 'HTTP ' . $httpCode,
|
||||
'data' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
231
Fmis/swapidc/swapidc_lxdapi/swapidc_lxdapi.php
Normal file
231
Fmis/swapidc/swapidc_lxdapi/swapidc_lxdapi.php
Normal file
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
/**
|
||||
* SWAPIDC-LXD对接插件 by xkatld
|
||||
*
|
||||
* @author xkatld
|
||||
* @version 2.0.2
|
||||
* @link https://github.com/xkatld/lxdapi-web-server
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/includes/lxd_api.php';
|
||||
|
||||
function swapidc_lxdapi_ConfigOptions() {
|
||||
return array(
|
||||
"CPU核心数" => array("Type" => "text", "Size" => "10"),
|
||||
"内存 (MB)" => array("Type" => "text", "Size" => "10"),
|
||||
"硬盘 (MB)" => array("Type" => "text", "Size" => "10"),
|
||||
"系统镜像" => array("Type" => "text", "Size" => "25"),
|
||||
"入站带宽 (Mbit)" => array("Type" => "text", "Size" => "10"),
|
||||
"出站带宽 (Mbit)" => array("Type" => "text", "Size" => "10"),
|
||||
"月流量限制 (GB)" => array("Type" => "text", "Size" => "10"),
|
||||
"IPv4地址池限制" => array("Type" => "text", "Size" => "10"),
|
||||
"IPv4端口映射限制" => array("Type" => "text", "Size" => "10"),
|
||||
"IPv6地址池限制" => array("Type" => "text", "Size" => "10"),
|
||||
"IPv6端口映射限制" => array("Type" => "text", "Size" => "10"),
|
||||
"反向代理限制" => array("Type" => "text", "Size" => "10"),
|
||||
"CPU使用率限制 (%)" => array("Type" => "text", "Size" => "10"),
|
||||
"磁盘读取限制 (MB/s)" => array("Type" => "text", "Size" => "10"),
|
||||
"磁盘写入限制 (MB/s)" => array("Type" => "text", "Size" => "10"),
|
||||
"最大进程数" => array("Type" => "text", "Size" => "10"),
|
||||
"嵌套虚拟化" => array("Type" => "text", "Size" => "10"),
|
||||
"Swap开关" => array("Type" => "text", "Size" => "10"),
|
||||
"特权模式" => array("Type" => "text", "Size" => "10")
|
||||
);
|
||||
}
|
||||
|
||||
function lxdapi_log($message) {
|
||||
$logFile = __DIR__ . '/lxdapi_debug.log';
|
||||
$time = date('Y-m-d H:i:s');
|
||||
file_put_contents($logFile, "[$time] $message\n", FILE_APPEND);
|
||||
}
|
||||
|
||||
function lxdapi_generate_password($length = 16) {
|
||||
$chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*';
|
||||
$password = '';
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$password .= $chars[rand(0, strlen($chars) - 1)];
|
||||
}
|
||||
return $password;
|
||||
}
|
||||
|
||||
function get_container_name($data) {
|
||||
if (!empty($data['注释'])) {
|
||||
return $data['注释'];
|
||||
}
|
||||
$serviceid = $data['serviceid'] ?? 0;
|
||||
if ($serviceid) {
|
||||
$result = full_query("SELECT 注释 FROM 服务 WHERE id = " . intval($serviceid));
|
||||
if ($row = mysqli_fetch_assoc($result)) {
|
||||
if (!empty($row['注释'])) {
|
||||
return $row['注释'];
|
||||
}
|
||||
}
|
||||
}
|
||||
return 'lxd' . rand(1000, 9999) . $data['serviceid'];
|
||||
}
|
||||
|
||||
function swapidc_lxdapi_CreateAccount($data) {
|
||||
lxdapi_log("开始创建账户,服务ID: {$data['serviceid']}");
|
||||
|
||||
try {
|
||||
$api = new LXD_API($data);
|
||||
$password = lxdapi_generate_password();
|
||||
$containerName = 'lxd' . rand(1000, 9999) . $data['serviceid'];
|
||||
|
||||
$requestData = [
|
||||
'name' => $containerName,
|
||||
'image' => $data['configoption4'] ?? 'alpine320',
|
||||
'username' => 'user' . $data['uid'],
|
||||
'password' => $password,
|
||||
'cpu' => (int)($data['configoption1'] ?? 1),
|
||||
'memory' => (int)($data['configoption2'] ?? 512),
|
||||
'disk' => (int)($data['configoption3'] ?? 1024),
|
||||
'ingress' => (int)($data['configoption5'] ?? 100),
|
||||
'egress' => (int)($data['configoption6'] ?? 100),
|
||||
'traffic_limit' => (int)($data['configoption7'] ?? 100),
|
||||
'ipv4_pool_limit' => (int)($data['configoption8'] ?? 0),
|
||||
'ipv4_mapping_limit' => (int)($data['configoption9'] ?? 0),
|
||||
'ipv6_pool_limit' => (int)($data['configoption10'] ?? 0),
|
||||
'ipv6_mapping_limit' => (int)($data['configoption11'] ?? 0),
|
||||
'reverse_proxy_limit' => (int)($data['configoption12'] ?? 0),
|
||||
'cpu_allowance' => (int)($data['configoption13'] ?? 50),
|
||||
'io_read' => (int)($data['configoption14'] ?? 100),
|
||||
'io_write' => (int)($data['configoption15'] ?? 50),
|
||||
'processes_limit' => (int)($data['configoption16'] ?? 512),
|
||||
'allow_nesting' => ($data['configoption17'] ?? 'true') === 'true',
|
||||
'memory_swap' => ($data['configoption18'] ?? 'true') === 'true',
|
||||
'privileged' => ($data['configoption19'] ?? 'false') === 'true',
|
||||
];
|
||||
|
||||
$result = $api->post('/api/system/containers', $requestData);
|
||||
|
||||
if (!$result['success']) {
|
||||
lxdapi_log("创建容器失败: " . ($result['message'] ?? '未知错误'));
|
||||
return $result['message'] ?? '创建容器失败';
|
||||
}
|
||||
|
||||
$ip = $result['data']['ipv4'] ?? 'DHCP';
|
||||
|
||||
update_query("服务", [
|
||||
"用户名" => "root",
|
||||
"密码" => encrypt($password),
|
||||
"专用IP" => $ip,
|
||||
"注释" => $containerName
|
||||
], ["id" => $data['serviceid']]);
|
||||
|
||||
lxdapi_log("容器创建成功: $containerName");
|
||||
return "成功";
|
||||
|
||||
} catch (Exception $e) {
|
||||
lxdapi_log("创建账户异常: " . $e->getMessage());
|
||||
return "创建失败: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function swapidc_lxdapi_SuspendAccount($data) {
|
||||
lxdapi_log("暂停账户,服务ID: {$data['serviceid']}");
|
||||
|
||||
try {
|
||||
$api = new LXD_API($data);
|
||||
$containerName = get_container_name($data);
|
||||
$result = $api->post('/api/system/containers/' . urlencode($containerName) . '/action?action=stop', []);
|
||||
return $result['success'] ? "成功" : ($result['message'] ?? '暂停失败');
|
||||
} catch (Exception $e) {
|
||||
return "暂停失败: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function swapidc_lxdapi_UnsuspendAccount($data) {
|
||||
lxdapi_log("解除暂停,服务ID: {$data['serviceid']}");
|
||||
|
||||
try {
|
||||
$api = new LXD_API($data);
|
||||
$containerName = get_container_name($data);
|
||||
$result = $api->post('/api/system/containers/' . urlencode($containerName) . '/action?action=start', []);
|
||||
return $result['success'] ? "成功" : ($result['message'] ?? '解除暂停失败');
|
||||
} catch (Exception $e) {
|
||||
return "解除暂停失败: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function swapidc_lxdapi_TerminateAccount($data) {
|
||||
lxdapi_log("删除账户,服务ID: {$data['serviceid']}");
|
||||
|
||||
try {
|
||||
$api = new LXD_API($data);
|
||||
$containerName = get_container_name($data);
|
||||
$result = $api->delete('/api/system/containers/' . urlencode($containerName));
|
||||
return $result['success'] ? "成功" : ($result['message'] ?? '删除失败');
|
||||
} catch (Exception $e) {
|
||||
return "删除失败: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function swapidc_lxdapi_ChangePassword($data) {
|
||||
lxdapi_log("修改密码,服务ID: {$data['serviceid']}");
|
||||
|
||||
try {
|
||||
$api = new LXD_API($data);
|
||||
$containerName = get_container_name($data);
|
||||
$newPassword = $data['password'] ?? lxdapi_generate_password();
|
||||
|
||||
$result = $api->post('/api/system/containers/' . urlencode($containerName) . '/action?action=reset-password', [
|
||||
'password' => $newPassword
|
||||
]);
|
||||
|
||||
if ($result['success']) {
|
||||
update_query("服务", ["密码" => encrypt($newPassword)], ["id" => $data['serviceid']]);
|
||||
return "成功";
|
||||
}
|
||||
|
||||
return $result['message'] ?? '修改密码失败';
|
||||
} catch (Exception $e) {
|
||||
return "修改密码失败: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function swapidc_lxdapi_ClientArea($data) {
|
||||
$js_code = "
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var iframe = document.querySelector('iframe[src*=\"container/dashboard\"]');
|
||||
if (!iframe) return;
|
||||
var btnGroup = iframe.closest('.btn-group');
|
||||
if (btnGroup) {
|
||||
btnGroup.parentNode.insertBefore(iframe, btnGroup.nextSibling);
|
||||
btnGroup.style.display = 'none';
|
||||
}
|
||||
iframe.style.cssText = 'width:100%;height:80vh;border:1px solid #ddd;border-radius:8px;margin-top:15px;display:block;';
|
||||
|
||||
var resetForm = document.querySelector('form#formrepass, form[action*=\"repass\"]');
|
||||
if (resetForm) resetForm.style.display = 'none';
|
||||
var resetLink = document.querySelector('a[href=\"#resetPass\"]');
|
||||
if (resetLink) resetLink.style.display = 'none';
|
||||
document.querySelectorAll('button, a').forEach(function(el) {
|
||||
if (el.textContent.includes('重置产品密码')) el.style.display = 'none';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
";
|
||||
|
||||
try {
|
||||
$api = new LXD_API($data);
|
||||
$containerName = get_container_name($data);
|
||||
$result = $api->get('/api/system/containers/' . urlencode($containerName) . '/credential');
|
||||
|
||||
if ($result['success'] && !empty($result['data']['access_code'])) {
|
||||
$accessCode = $result['data']['access_code'];
|
||||
$serverHost = $data['serverip'] ?? '';
|
||||
$serverPort = $data['serverport'] ?? '8443';
|
||||
$iframeUrl = 'https://' . $serverHost . ':' . $serverPort . '/container/dashboard/lite?hash=' . $accessCode;
|
||||
$panel_html = "<iframe src='{$iframeUrl}' style='width:100%;height:80vh;border:none;' frameborder='0' allowfullscreen></iframe>";
|
||||
return array("", "</li></ul>{$js_code}{$panel_html}<ul>");
|
||||
} else {
|
||||
$error_html = "<div style='background:#f8d7da;border:1px solid #f5c6cb;border-radius:6px;padding:15px;margin:10px 0;color:#721c24;'><strong>无法加载管理面板</strong><br>" . ($result['message'] ?? '获取访问码失败') . "</div>";
|
||||
return array("", "</li></ul>{$js_code}{$error_html}<ul>");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$error_html = "<div style='background:#f8d7da;border:1px solid #f5c6cb;border-radius:6px;padding:15px;margin:10px 0;color:#721c24;'><strong>连接失败</strong><br>" . $e->getMessage() . "</div>";
|
||||
return array("", "</li></ul>{$js_code}{$error_html}<ul>");
|
||||
}
|
||||
}
|
||||
7
Fmis/swapidc/swapidc_lxdapi/version.php
Normal file
7
Fmis/swapidc/swapidc_lxdapi/version.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
return array(
|
||||
'key'=>'swapidc_lxdapi',
|
||||
'name'=>'SWAPIDC-LXD对接插件 by xkatld',
|
||||
'version'=>'2.0.2'
|
||||
);
|
||||
?>
|
||||
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* @package WHMCS-LXD对接插件 by xkatld
|
||||
* @author xkatld
|
||||
* @version v2.0.2
|
||||
* @version v2.0.3
|
||||
* @link https://github.com/xkatld/lxdapi-web-server
|
||||
*/
|
||||
|
||||
@@ -21,7 +21,7 @@ function lxdapiserver_MetaData()
|
||||
{
|
||||
return [
|
||||
'DisplayName' => 'WHMCS-LXD对接插件 by xkatld',
|
||||
'APIVersion' => 'v2.0.2',
|
||||
'APIVersion' => 'v2.0.3',
|
||||
'RequiresServer' => true,
|
||||
'DefaultNonSSLPort' => '8443',
|
||||
'DefaultSSLPort' => '8443',
|
||||
@@ -5,7 +5,7 @@ use app\common\model\HostModel;
|
||||
function lxdapiserver_MetaData(){
|
||||
return [
|
||||
'DisplayName' => '魔方财务V10-LXD对接插件 by xkatld',
|
||||
'Version' => 'v2.0.2',
|
||||
'Version' => 'v2.0.3',
|
||||
'HelpDoc' => 'https://github.com/xkatld/lxdapi-web-server',
|
||||
];
|
||||
}
|
||||
@@ -14,20 +14,22 @@ exec &> >(tee -a "$LOG_FILE")
|
||||
set -e
|
||||
set -u
|
||||
|
||||
ZFS_VER="2.3.0"
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "错误: 此脚本需要以root权限运行。请使用 'sudo'。" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DEBIAN_VER=$(cat /etc/debian_version | cut -d. -f1)
|
||||
ARCH=$(uname -m)
|
||||
|
||||
case "$DEBIAN_VER" in
|
||||
11) DEBIAN_NAME="Debian 11 (Bullseye)" ;;
|
||||
12) DEBIAN_NAME="Debian 12 (Bookworm)" ;;
|
||||
13|trixie) DEBIAN_NAME="Debian 13 (Trixie)" ;;
|
||||
11)
|
||||
DEBIAN_NAME="Debian 11 (Bullseye)"
|
||||
ZFS_VER="2.2.9"
|
||||
;;
|
||||
12)
|
||||
DEBIAN_NAME="Debian 12 (Bookworm)"
|
||||
ZFS_VER="2.3.5"
|
||||
;;
|
||||
13|trixie)
|
||||
DEBIAN_NAME="Debian 13 (Trixie)"
|
||||
ZFS_VER="2.3.5"
|
||||
;;
|
||||
*)
|
||||
echo "错误: 不支持的 Debian 版本: $DEBIAN_VER"
|
||||
exit 1
|
||||
@@ -6,6 +6,8 @@ YELLOW='\033[0;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
LXC="/snap/bin/lxc"
|
||||
|
||||
ok() { echo -e "${GREEN}[OK]${NC} $1"; }
|
||||
err() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
@@ -15,20 +17,6 @@ reading() {
|
||||
read -rp "$(echo -e "${GREEN}[INPUT]${NC} $1")" "$2"
|
||||
}
|
||||
|
||||
check_root() {
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
err "请使用 root 用户运行此脚本"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_lxd() {
|
||||
if ! command -v lxc &>/dev/null; then
|
||||
err "未检测到 LXD,请先安装 LXD"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
detect_arch() {
|
||||
sys_arch=$(uname -m)
|
||||
case $sys_arch in
|
||||
@@ -46,39 +34,48 @@ detect_arch() {
|
||||
ok "系统架构: $ARCH"
|
||||
}
|
||||
|
||||
IMAGES_BASE_URL="https://github.com/xkatld/zjmf-lxd-server/releases/download/images"
|
||||
IMAGES_BASE_URL="https://github.com/xkatld/lxdapi-web-server/releases/download/image"
|
||||
|
||||
declare -A IMAGE_MAP
|
||||
IMAGE_MAP=(
|
||||
[1]="alma8" [2]="alma9" [3]="alma10"
|
||||
[4]="alpine319" [5]="alpine320" [6]="alpine321" [7]="alpine322" [8]="alpineEdge"
|
||||
[9]="amazon2023"
|
||||
[10]="centos9" [11]="centos10"
|
||||
[12]="debian11" [13]="debian12" [14]="debian13"
|
||||
[15]="fedora41" [16]="fedora42"
|
||||
[17]="oracle8" [18]="oracle9"
|
||||
[19]="rocky8" [20]="rocky9" [21]="rocky10"
|
||||
[22]="suse155" [23]="suse156" [24]="suseTumbleweed"
|
||||
[25]="ubuntu2204" [26]="ubuntu2404" [27]="ubuntu2410"
|
||||
declare -a IMAGE_LIST=(
|
||||
"almalinux-8"
|
||||
"almalinux-9"
|
||||
"alpine-320"
|
||||
"alpine-321"
|
||||
"alpine-322"
|
||||
"archlinux-latest"
|
||||
"centos-9-Stream"
|
||||
"debian-11"
|
||||
"debian-12"
|
||||
"debian-13"
|
||||
"fedora-42"
|
||||
"fedora-43"
|
||||
"opensuse-156"
|
||||
"opensuse-tumbleweed"
|
||||
"rockylinux-8"
|
||||
"rockylinux-9"
|
||||
"ubuntu-2204"
|
||||
"ubuntu-2404"
|
||||
)
|
||||
|
||||
download_and_import() {
|
||||
local image_name="$1"
|
||||
local image_url="${IMAGES_BASE_URL}/${image_name}-${ARCH}.tar.gz"
|
||||
local image_type="$2"
|
||||
local image_url="${IMAGES_BASE_URL}/${image_name}-${ARCH}-${image_type}.tar.gz"
|
||||
|
||||
info "下载: ${image_name}-${ARCH}.tar.gz"
|
||||
info "下载: ${image_name}-${ARCH}-${image_type}.tar.gz"
|
||||
|
||||
local temp_file=$(mktemp)
|
||||
if wget -q --show-progress -O "$temp_file" "$image_url" 2>&1; then
|
||||
info "导入到 LXD..."
|
||||
if lxc image import "$temp_file" --alias "$image_name" 2>/dev/null; then
|
||||
ok "成功导入: $image_name"
|
||||
local alias="${image_name}-${image_type}"
|
||||
if $LXC image import "$temp_file" --alias "$alias" 2>/dev/null; then
|
||||
ok "成功导入: $alias"
|
||||
else
|
||||
warn "导入失败: $image_name"
|
||||
warn "导入失败: $alias"
|
||||
fi
|
||||
rm -f "$temp_file"
|
||||
else
|
||||
warn "下载失败: ${image_name}"
|
||||
warn "下载失败: ${image_name}-${ARCH}-${image_type}"
|
||||
rm -f "$temp_file"
|
||||
fi
|
||||
}
|
||||
@@ -86,12 +83,10 @@ download_and_import() {
|
||||
show_image_list() {
|
||||
echo
|
||||
echo "============================================================================================================"
|
||||
echo " 1) alma8 2) alma9 3) alma10 4) alpine319 5) alpine320 "
|
||||
echo " 6) alpine321 7) alpine322 8) alpineEdge 9) amazon2023 10) centos9 "
|
||||
echo "11) centos10 12) debian11 13) debian12 14) debian13 15) fedora41 "
|
||||
echo "16) fedora42 17) oracle8 18) oracle9 19) rocky8 20) rocky9 "
|
||||
echo "21) rocky10 22) suse155 23) suse156 24) suseTumbleweed "
|
||||
echo "25) ubuntu2204 26) ubuntu2404 27) ubuntu2410 "
|
||||
echo " 1) almalinux-8 2) almalinux-9 3) alpine-320 4) alpine-321 5) alpine-322"
|
||||
echo " 6) archlinux-latest 7) centos-9-Stream 8) debian-11 9) debian-12 10) debian-13"
|
||||
echo "11) fedora-42 12) fedora-43 13) opensuse-156 14) opensuse-tumbleweed"
|
||||
echo "15) rockylinux-8 16) rockylinux-9 17) ubuntu-2204 18) ubuntu-2404"
|
||||
echo "============================================================================================================"
|
||||
echo
|
||||
}
|
||||
@@ -101,18 +96,34 @@ menu_import() {
|
||||
info "=== 导入镜像 ==="
|
||||
show_image_list
|
||||
|
||||
reading "输入编号,多个用逗号分隔,或 all 全部导入 [2,5,13,26]: " image_choices
|
||||
image_choices=${image_choices:-"2,5,13,26"}
|
||||
reading "输入编号,多个用逗号分隔,或 all 全部导入 [8,9,17,18]: " image_choices
|
||||
image_choices=${image_choices:-"8,9,17,18"}
|
||||
|
||||
while true; do
|
||||
reading "选择镜像类型 lxc/kvm [lxc]: " image_type
|
||||
image_type=${image_type:-lxc}
|
||||
if [[ "$image_type" =~ ^(lxc|kvm)$ ]]; then
|
||||
break
|
||||
else
|
||||
warn "请输入 lxc 或 kvm"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$image_type" == "kvm" && "$ARCH" == "arm64" ]]; then
|
||||
warn "KVM 镜像不支持 arm64 架构"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$image_choices" == "all" ]]; then
|
||||
selected_images=(${IMAGE_MAP[@]})
|
||||
selected_images=("${IMAGE_LIST[@]}")
|
||||
else
|
||||
IFS=',' read -ra choices <<< "$image_choices"
|
||||
selected_images=()
|
||||
for choice in "${choices[@]}"; do
|
||||
choice=$(echo "$choice" | xargs)
|
||||
if [[ -n "${IMAGE_MAP[$choice]}" ]]; then
|
||||
selected_images+=("${IMAGE_MAP[$choice]}")
|
||||
idx=$((choice - 1))
|
||||
if [[ $idx -ge 0 && $idx -lt ${#IMAGE_LIST[@]} ]]; then
|
||||
selected_images+=("${IMAGE_LIST[$idx]}")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
@@ -122,14 +133,14 @@ menu_import() {
|
||||
return
|
||||
fi
|
||||
|
||||
ok "已选择 ${#selected_images[@]} 个镜像"
|
||||
ok "已选择 ${#selected_images[@]} 个镜像 (${image_type})"
|
||||
echo
|
||||
|
||||
current=0
|
||||
for img in "${selected_images[@]}"; do
|
||||
((current++))
|
||||
echo "[$current/${#selected_images[@]}]"
|
||||
download_and_import "$img"
|
||||
download_and_import "$img" "$image_type"
|
||||
echo
|
||||
done
|
||||
}
|
||||
@@ -137,13 +148,13 @@ menu_import() {
|
||||
menu_list() {
|
||||
echo
|
||||
info "=== 已有镜像 ==="
|
||||
lxc image list
|
||||
$LXC image list
|
||||
}
|
||||
|
||||
menu_delete() {
|
||||
echo
|
||||
info "=== 删除镜像 ==="
|
||||
lxc image list
|
||||
$LXC image list
|
||||
echo
|
||||
reading "输入要删除的镜像别名或指纹: " image_id
|
||||
if [ -z "$image_id" ]; then
|
||||
@@ -153,7 +164,7 @@ menu_delete() {
|
||||
warn "确认删除镜像 $image_id?"
|
||||
reading "确认?(y/n) [n]: " confirm
|
||||
if [[ "$confirm" =~ ^[yY]$ ]]; then
|
||||
if lxc image delete "$image_id"; then
|
||||
if $LXC image delete "$image_id"; then
|
||||
ok "镜像已删除"
|
||||
else
|
||||
err "删除失败"
|
||||
@@ -187,7 +198,5 @@ main_menu() {
|
||||
done
|
||||
}
|
||||
|
||||
check_root
|
||||
check_lxd
|
||||
detect_arch
|
||||
main_menu
|
||||
327
Shell/lxd_install.sh
Normal file
327
Shell/lxd_install.sh
Normal file
@@ -0,0 +1,327 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd /root >/dev/null 2>&1
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
REGEX=("debian|astra" "ubuntu")
|
||||
RELEASE=("Debian" "Ubuntu")
|
||||
CMD=("$(grep -i pretty_name /etc/os-release 2>/dev/null | cut -d \" -f2)" "$(lsb_release -sd 2>/dev/null)")
|
||||
SYS="${CMD[0]}"
|
||||
[[ -n $SYS ]] || exit 1
|
||||
|
||||
for ((int = 0; int < ${#REGEX[@]}; int++)); do
|
||||
if [[ $(echo "$SYS" | tr '[:upper:]' '[:lower:]') =~ ${REGEX[int]} ]]; then
|
||||
SYSTEM="${RELEASE[int]}"
|
||||
[[ -n $SYSTEM ]] && break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$SYSTEM" != "Debian" && "$SYSTEM" != "Ubuntu" ]]; then
|
||||
echo -e "${RED}[ERR]${NC} 此脚本仅支持 Debian 和 Ubuntu 系统"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$SYSTEM" == "Debian" ]]; then
|
||||
OS_VERSION=$(cat /etc/debian_version | cut -d. -f1)
|
||||
elif [[ "$SYSTEM" == "Ubuntu" ]]; then
|
||||
OS_VERSION=$(grep VERSION_ID /etc/os-release | cut -d'"' -f2 | cut -d. -f1)
|
||||
fi
|
||||
|
||||
RECOMMENDED=false
|
||||
if [[ "$SYSTEM" == "Debian" && ("$OS_VERSION" == "12" || "$OS_VERSION" == "13") ]]; then
|
||||
RECOMMENDED=true
|
||||
elif [[ "$SYSTEM" == "Ubuntu" && ("$OS_VERSION" == "24" || "$OS_VERSION" == "25") ]]; then
|
||||
RECOMMENDED=true
|
||||
fi
|
||||
|
||||
if [[ "$RECOMMENDED" != "true" ]]; then
|
||||
echo -e "${YELLOW}[WARN]${NC} 当前系统: $SYSTEM $OS_VERSION"
|
||||
echo -e "${YELLOW}[WARN]${NC} 推荐使用: Debian 12/13 或 Ubuntu 24/25"
|
||||
read -rp "$(echo -e "${YELLOW}是否继续安装?(y/n) [n]:${NC}")" confirm_install
|
||||
confirm_install=${confirm_install:-n}
|
||||
if [[ ! "$confirm_install" =~ ^[yY]$ ]]; then
|
||||
echo -e "${RED}[ERR]${NC} 安装已取消"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
log() { echo -e "$1"; }
|
||||
ok() { log "${GREEN}[OK]${NC} $1"; }
|
||||
info() { log "${BLUE}[INFO]${NC} $1"; }
|
||||
warn() { log "${YELLOW}[WARN]${NC} $1"; }
|
||||
err() { log "${RED}[ERR]${NC} $1"; exit 1; }
|
||||
|
||||
reading() { read -rp "$(echo -e "${GREEN}$1${NC}")" "$2"; }
|
||||
|
||||
install_package() {
|
||||
package_name=$1
|
||||
if dpkg -l 2>/dev/null | grep -q "^ii.*$package_name"; then
|
||||
ok "$package_name 已安装"
|
||||
else
|
||||
apt-get install -y $package_name >/dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
apt-get install -y $package_name --fix-missing >/dev/null 2>&1
|
||||
fi
|
||||
if dpkg -l 2>/dev/null | grep -q "^ii.*$package_name"; then
|
||||
ok "$package_name 已安装"
|
||||
else
|
||||
warn "$package_name 安装失败"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
get_available_space() {
|
||||
local available_space
|
||||
available_space=$(df -BG / | awk 'NR==2 {gsub("G","",$4); print $4}')
|
||||
echo "$available_space"
|
||||
}
|
||||
|
||||
install_lxd() {
|
||||
lxd_snap=$(dpkg -l | awk '/^[hi]i/{print $2}' | grep -ow snap)
|
||||
lxd_snapd=$(dpkg -l | awk '/^[hi]i/{print $2}' | grep -ow snapd)
|
||||
if [[ "$lxd_snap" =~ ^snap.* ]] && [[ "$lxd_snapd" =~ ^snapd.* ]]; then
|
||||
ok "snap 已安装"
|
||||
else
|
||||
info "开始安装 snap..."
|
||||
apt-get update >/dev/null 2>&1
|
||||
install_package snapd
|
||||
fi
|
||||
snap_core=$(snap list core 2>/dev/null)
|
||||
snap_lxd=$(snap list lxd 2>/dev/null)
|
||||
if [[ "$snap_core" =~ core.* ]] && [[ "$snap_lxd" =~ lxd.* ]]; then
|
||||
ok "LXD 已安装"
|
||||
lxd_lxc_detect=$(lxc list 2>/dev/null)
|
||||
if [[ "$lxd_lxc_detect" =~ "snap-update-ns failed with code1".* ]]; then
|
||||
systemctl restart apparmor
|
||||
snap restart lxd
|
||||
else
|
||||
ok "环境检测无问题"
|
||||
fi
|
||||
else
|
||||
info "开始安装 LXD..."
|
||||
snap install lxd --channel=latest/stable 2>/dev/null
|
||||
if [[ $? -ne 0 ]]; then
|
||||
snap remove lxd 2>/dev/null
|
||||
snap install core 2>/dev/null
|
||||
snap install lxd --channel=latest/stable 2>/dev/null
|
||||
fi
|
||||
snap alias lxd.lxc lxc 2>/dev/null
|
||||
snap alias lxd.lxd lxd 2>/dev/null
|
||||
if [ ! -f /etc/profile.d/snap.sh ]; then
|
||||
echo 'export PATH=$PATH:/snap/bin' > /etc/profile.d/snap.sh
|
||||
fi
|
||||
export PATH=$PATH:/snap/bin
|
||||
if ! command -v lxc >/dev/null 2>&1; then
|
||||
err 'lxc 路径有问题,请检查 snap alias'
|
||||
fi
|
||||
ok "LXD 安装完成"
|
||||
fi
|
||||
|
||||
if dpkg -l lxcfs 2>/dev/null | grep -q "^ii"; then
|
||||
warn "检测到 deb 版 lxcfs,正在移除..."
|
||||
systemctl stop lxcfs 2>/dev/null || true
|
||||
systemctl disable lxcfs 2>/dev/null || true
|
||||
apt-get remove -y lxcfs >/dev/null 2>&1
|
||||
ok "deb 版 lxcfs 已移除"
|
||||
fi
|
||||
|
||||
lxd_version=$(lxd --version 2>/dev/null)
|
||||
info "LXD 版本: $lxd_version"
|
||||
if [[ ! "$lxd_version" =~ ^6\. ]]; then
|
||||
warn "当前 LXD 版本 $lxd_version 不兼容,推荐使用 6.x 版本"
|
||||
reading "是否继续?(y/n) [y]:" version_confirm
|
||||
version_confirm=${version_confirm:-y}
|
||||
if [[ ! "$version_confirm" =~ ^[yY]$ ]]; then
|
||||
err "已取消安装"
|
||||
fi
|
||||
else
|
||||
ok "LXD 版本兼容"
|
||||
fi
|
||||
|
||||
info "配置 LXD..."
|
||||
snap set lxd lxcfs.flags="-l" 2>/dev/null
|
||||
snap set lxd daemon.debug=false 2>/dev/null
|
||||
snap restart lxd 2>/dev/null
|
||||
sleep 3
|
||||
ok "LXD 已配置"
|
||||
}
|
||||
|
||||
init_lxd_network() {
|
||||
if ! /snap/bin/lxc network show lxdbr0 &>/dev/null; then
|
||||
info "创建默认网络 lxdbr0..."
|
||||
/snap/bin/lxc network create lxdbr0
|
||||
ok "网络 lxdbr0 创建成功"
|
||||
else
|
||||
ok "网络 lxdbr0 已存在"
|
||||
fi
|
||||
|
||||
if ! /snap/bin/lxc profile device show default 2>/dev/null | grep -q "eth0"; then
|
||||
info "配置 default profile 网络设备..."
|
||||
/snap/bin/lxc profile device add default eth0 nic network=lxdbr0 name=eth0
|
||||
ok "网络设备已添加到 default profile"
|
||||
fi
|
||||
}
|
||||
|
||||
setup_storage() {
|
||||
info "配置存储池..."
|
||||
|
||||
if /snap/bin/lxc storage show default &>/dev/null; then
|
||||
ok "存储池 default 已存在"
|
||||
/snap/bin/lxc storage list
|
||||
return 0
|
||||
fi
|
||||
|
||||
available_space=$(get_available_space)
|
||||
info "当前可用磁盘空间: ${available_space}GB"
|
||||
|
||||
while true; do
|
||||
reading "请选择存储后端 zfs/btrfs/lvm [zfs]:" storage_driver
|
||||
storage_driver=${storage_driver:-zfs}
|
||||
if [[ "$storage_driver" =~ ^(zfs|btrfs|lvm)$ ]]; then
|
||||
break
|
||||
else
|
||||
warn "请输入 zfs、btrfs 或 lvm"
|
||||
fi
|
||||
done
|
||||
|
||||
case "$storage_driver" in
|
||||
zfs)
|
||||
if ! command -v zpool &>/dev/null; then
|
||||
info "安装 ZFS..."
|
||||
if [[ "$SYSTEM" == "Ubuntu" ]]; then
|
||||
install_package zfsutils-linux
|
||||
else
|
||||
bash <(curl -sL https://raw.githubusercontent.com/xkatld/lxdapi-web-server/refs/heads/v2.1.0-vpsm.link/Shell/debian_zfs.sh)
|
||||
fi
|
||||
fi
|
||||
info "配置 LXD 使用系统 ZFS..."
|
||||
snap set lxd zfs.external=true
|
||||
snap restart lxd
|
||||
sleep 3
|
||||
;;
|
||||
btrfs)
|
||||
install_package btrfs-progs
|
||||
;;
|
||||
lvm)
|
||||
install_package lvm2
|
||||
;;
|
||||
esac
|
||||
|
||||
reading "请输入存储池大小(GB) [${available_space}]:" pool_size
|
||||
pool_size=${pool_size:-$available_space}
|
||||
|
||||
info "创建 default 存储池 (${storage_driver}, ${pool_size}GB)..."
|
||||
/snap/bin/lxc storage create default ${storage_driver} size=${pool_size}GB
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
ok "存储池 default 创建成功"
|
||||
if ! /snap/bin/lxc profile device show default 2>/dev/null | grep -q "root"; then
|
||||
/snap/bin/lxc profile device add default root disk path=/ pool=default
|
||||
ok "存储池已添加到 default profile"
|
||||
fi
|
||||
else
|
||||
err "存储池创建失败"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
echo
|
||||
echo "========================================"
|
||||
echo " LXD 安装脚本"
|
||||
echo " by Github-xkatld"
|
||||
echo "========================================"
|
||||
echo
|
||||
|
||||
echo "======== 步骤 1/5: 检测系统 ========"
|
||||
info "系统: $SYSTEM $OS_VERSION"
|
||||
if [[ "$RECOMMENDED" == "true" ]]; then
|
||||
ok "系统版本符合推荐"
|
||||
else
|
||||
warn "建议使用 Debian 12/13 或 Ubuntu 24/25"
|
||||
fi
|
||||
|
||||
if [[ "$SYSTEM" == "Debian" ]]; then
|
||||
echo
|
||||
warn "Debian 使用 ZFS 存储需要编译安装,耗时较长"
|
||||
warn "如需使用 ZFS,推荐使用 Ubuntu 系统"
|
||||
reading "是否继续使用 Debian?(y/n) [y]:" debian_confirm
|
||||
debian_confirm=${debian_confirm:-y}
|
||||
if [[ ! "$debian_confirm" =~ ^[yY]$ ]]; then
|
||||
info "已取消安装"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
echo
|
||||
|
||||
echo "======== 步骤 2/5: 安装 LXD ========"
|
||||
reading "是否安装 LXD?(y/n) [y]:" step2_confirm
|
||||
step2_confirm=${step2_confirm:-y}
|
||||
if [[ "$step2_confirm" =~ ^[yY]$ ]]; then
|
||||
install_lxd
|
||||
ok "LXD 安装完成"
|
||||
else
|
||||
info "已跳过 LXD 安装"
|
||||
fi
|
||||
echo
|
||||
|
||||
echo "======== 步骤 3/5: 网络配置 ========"
|
||||
reading "是否配置网络?(y/n) [y]:" step3_confirm
|
||||
step3_confirm=${step3_confirm:-y}
|
||||
if [[ "$step3_confirm" =~ ^[yY]$ ]]; then
|
||||
init_lxd_network
|
||||
reading "是否开启 IPv4 分配,分配NAT和独立IP需要开启 (y/n) [y]:" ipv4_dhcp
|
||||
ipv4_dhcp=${ipv4_dhcp:-y}
|
||||
if [[ ! "$ipv4_dhcp" =~ ^[yY]$ ]]; then
|
||||
/snap/bin/lxc network set lxdbr0 ipv4.dhcp false
|
||||
ok "IPv4 分配已关闭"
|
||||
else
|
||||
ok "IPv4 分配已开启"
|
||||
fi
|
||||
reading "是否开启 IPv6 分配,分配NAT和独立IP需要开启 (y/n) [y]:" ipv6_dhcp
|
||||
ipv6_dhcp=${ipv6_dhcp:-y}
|
||||
if [[ ! "$ipv6_dhcp" =~ ^[yY]$ ]]; then
|
||||
/snap/bin/lxc network set lxdbr0 ipv6.dhcp false
|
||||
/snap/bin/lxc network set lxdbr0 ipv6.address none
|
||||
ok "IPv6 分配已关闭"
|
||||
else
|
||||
ok "IPv6 分配已开启"
|
||||
fi
|
||||
ok "网络配置完成"
|
||||
else
|
||||
info "已跳过网络配置"
|
||||
fi
|
||||
echo
|
||||
|
||||
echo "======== 步骤 4/5: 存储配置 ========"
|
||||
info "配置 default 存储池,首次安装推荐配置"
|
||||
reading "是否配置存储池?(y/n) [y]:" step4_confirm
|
||||
step4_confirm=${step4_confirm:-y}
|
||||
if [[ "$step4_confirm" =~ ^[yY]$ ]]; then
|
||||
setup_storage
|
||||
ok "存储配置完成"
|
||||
else
|
||||
info "已跳过存储配置"
|
||||
fi
|
||||
echo
|
||||
|
||||
echo "======== 步骤 5/5: 完成 ========"
|
||||
echo
|
||||
echo "========================================"
|
||||
echo " LXD 安装完成"
|
||||
echo "========================================"
|
||||
echo
|
||||
info "LXD 版本: $(lxd --version 2>/dev/null)"
|
||||
echo
|
||||
info "===== 网络配置 ====="
|
||||
lxc network list 2>/dev/null || warn "无法获取网络列表"
|
||||
echo
|
||||
info "===== 存储配置 ====="
|
||||
lxc storage list 2>/dev/null || warn "无法获取存储列表"
|
||||
}
|
||||
|
||||
main
|
||||
@@ -26,106 +26,14 @@ if [[ "$SYSTEM" != "Debian" && "$SYSTEM" != "Ubuntu" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$SYSTEM" == "Debian" ]]; then
|
||||
OS_VERSION=$(cat /etc/debian_version | cut -d. -f1)
|
||||
elif [[ "$SYSTEM" == "Ubuntu" ]]; then
|
||||
OS_VERSION=$(grep VERSION_ID /etc/os-release | cut -d'"' -f2 | cut -d. -f1)
|
||||
fi
|
||||
|
||||
RECOMMENDED=false
|
||||
if [[ "$SYSTEM" == "Debian" && ("$OS_VERSION" == "12" || "$OS_VERSION" == "13") ]]; then
|
||||
RECOMMENDED=true
|
||||
elif [[ "$SYSTEM" == "Ubuntu" && ("$OS_VERSION" == "24" || "$OS_VERSION" == "25") ]]; then
|
||||
RECOMMENDED=true
|
||||
fi
|
||||
|
||||
if [[ "$RECOMMENDED" != "true" ]]; then
|
||||
echo -e "${YELLOW}[WARN]${NC} 当前系统: $SYSTEM $OS_VERSION"
|
||||
echo -e "${YELLOW}[WARN]${NC} 推荐使用: Debian 12/13 或 Ubuntu 24/25"
|
||||
read -rp "$(echo -e "${YELLOW}是否继续安装?(y/n) [n]:${NC}")" confirm_install
|
||||
confirm_install=${confirm_install:-n}
|
||||
if [[ ! "$confirm_install" =~ ^[yY]$ ]]; then
|
||||
echo -e "${RED}[ERR]${NC} 安装已取消"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
if [ ! -d "/usr/local/bin" ]; then
|
||||
mkdir -p /usr/local/bin
|
||||
fi
|
||||
|
||||
log() { echo -e "$1"; }
|
||||
ok() { log "${GREEN}[OK]${NC} $1"; }
|
||||
info() { log "${BLUE}[INFO]${NC} $1"; }
|
||||
warn() { log "${YELLOW}[WARN]${NC} $1"; }
|
||||
err() { log "${RED}[ERR]${NC} $1"; exit 1; }
|
||||
|
||||
print_step() {
|
||||
local step=$1
|
||||
local total=$2
|
||||
local title=$3
|
||||
echo
|
||||
echo "========================================"
|
||||
echo " 步骤 $step/$total: $title"
|
||||
echo "========================================"
|
||||
echo
|
||||
}
|
||||
|
||||
reading() { read -rp "$(echo -e "${GREEN}$1${NC}")" "$2"; }
|
||||
|
||||
sed_compatible() {
|
||||
if echo "test" | sed -E 's/test/ok/' >/dev/null 2>&1; then
|
||||
sed -E "$@"
|
||||
else
|
||||
sed -r "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
service_manager() {
|
||||
local action=$1
|
||||
local service_name=$2
|
||||
case "$action" in
|
||||
enable)
|
||||
systemctl enable "$service_name" 2>/dev/null
|
||||
;;
|
||||
disable)
|
||||
systemctl disable "$service_name" 2>/dev/null
|
||||
;;
|
||||
start)
|
||||
systemctl start "$service_name" 2>/dev/null
|
||||
;;
|
||||
stop)
|
||||
systemctl stop "$service_name" 2>/dev/null
|
||||
;;
|
||||
restart)
|
||||
systemctl restart "$service_name" 2>/dev/null
|
||||
;;
|
||||
daemon-reload)
|
||||
systemctl daemon-reload 2>/dev/null
|
||||
;;
|
||||
is-active)
|
||||
systemctl is-active --quiet "$service_name" 2>/dev/null
|
||||
return $?
|
||||
;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
set_locale() {
|
||||
utf8_locale=$(locale -a 2>/dev/null | grep -i -m 1 -E "utf8|UTF-8")
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
if [[ -z "$utf8_locale" ]]; then
|
||||
warn "未找到 UTF-8 语言环境"
|
||||
else
|
||||
export LC_ALL="$utf8_locale"
|
||||
export LANG="$utf8_locale"
|
||||
export LANGUAGE="$utf8_locale"
|
||||
ok "语言环境设置为 $utf8_locale"
|
||||
fi
|
||||
}
|
||||
|
||||
install_package() {
|
||||
package_name=$1
|
||||
if dpkg -l 2>/dev/null | grep -q "^ii.*$package_name"; then
|
||||
@@ -143,174 +51,28 @@ install_package() {
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
get_available_space() {
|
||||
local available_space
|
||||
available_space=$(df -BG / | awk 'NR==2 {gsub("G","",$4); print $4}')
|
||||
echo "$available_space"
|
||||
}
|
||||
|
||||
install_base_packages() {
|
||||
info "更新软件包列表..."
|
||||
apt-get update >/dev/null 2>&1
|
||||
apt-get autoremove -y >/dev/null 2>&1
|
||||
install_package wget
|
||||
install_package curl
|
||||
install_package sudo
|
||||
install_package unzip
|
||||
install_package iptables-persistent
|
||||
install_package nftables
|
||||
install_package nginx
|
||||
|
||||
if dpkg -l lxcfs 2>/dev/null | grep -q "^ii"; then
|
||||
warn "检测到 deb 版 lxcfs,正在移除以避免与 snap 版 LXD 冲突..."
|
||||
systemctl stop lxcfs 2>/dev/null || true
|
||||
systemctl disable lxcfs 2>/dev/null || true
|
||||
apt-get remove -y lxcfs >/dev/null 2>&1
|
||||
ok "deb 版 lxcfs 已移除,将使用 snap 版 LXD 内置的 lxcfs"
|
||||
info "安装基础软件包..."
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y unzip e2fsprogs bc nftables fdisk parted iptables-persistent nginx >/dev/null 2>&1
|
||||
ok "软件包安装完成"
|
||||
|
||||
systemctl enable nftables >/dev/null 2>&1
|
||||
systemctl start nftables >/dev/null 2>&1
|
||||
ok "nftables 已启动"
|
||||
|
||||
if command -v lxc &>/dev/null && lxc network show lxdbr0 &>/dev/null; then
|
||||
lxc network set lxdbr0 ipv4.nat true 2>/dev/null
|
||||
lxc network set lxdbr0 ipv6.nat true 2>/dev/null
|
||||
ok "LXD NAT 规则已重建"
|
||||
fi
|
||||
|
||||
if systemctl is-active --quiet nginx; then
|
||||
ok "nginx 服务已运行"
|
||||
else
|
||||
service_manager start nginx
|
||||
service_manager enable nginx
|
||||
ok "nginx 服务已启动并设置为自动启动"
|
||||
fi
|
||||
}
|
||||
|
||||
install_lxd() {
|
||||
lxd_snap=$(dpkg -l | awk '/^[hi]i/{print $2}' | grep -ow snap)
|
||||
lxd_snapd=$(dpkg -l | awk '/^[hi]i/{print $2}' | grep -ow snapd)
|
||||
if [[ "$lxd_snap" =~ ^snap.* ]] && [[ "$lxd_snapd" =~ ^snapd.* ]]; then
|
||||
ok "snap 已安装"
|
||||
else
|
||||
info "开始安装 snap..."
|
||||
apt-get update >/dev/null 2>&1
|
||||
install_package snapd
|
||||
fi
|
||||
snap_core=$(snap list core 2>/dev/null)
|
||||
snap_lxd=$(snap list lxd 2>/dev/null)
|
||||
if [[ "$snap_core" =~ core.* ]] && [[ "$snap_lxd" =~ lxd.* ]]; then
|
||||
ok "LXD 已安装"
|
||||
lxd_lxc_detect=$(lxc list 2>/dev/null)
|
||||
if [[ "$lxd_lxc_detect" =~ "snap-update-ns failed with code1".* ]]; then
|
||||
service_manager restart apparmor
|
||||
snap restart lxd
|
||||
else
|
||||
ok "环境检测无问题"
|
||||
fi
|
||||
else
|
||||
info "开始安装 LXD..."
|
||||
snap install lxd --channel=latest/stable 2>/dev/null
|
||||
if [[ $? -ne 0 ]]; then
|
||||
snap remove lxd 2>/dev/null
|
||||
snap install core 2>/dev/null
|
||||
snap install lxd --channel=latest/stable 2>/dev/null
|
||||
fi
|
||||
snap alias lxd.lxc lxc 2>/dev/null
|
||||
snap alias lxd.lxd lxd 2>/dev/null
|
||||
if [ ! -f /etc/profile.d/snap.sh ]; then
|
||||
echo 'export PATH=$PATH:/snap/bin' > /etc/profile.d/snap.sh
|
||||
fi
|
||||
export PATH=$PATH:/snap/bin
|
||||
if ! command -v lxc >/dev/null 2>&1; then
|
||||
err 'lxc 路径有问题,请检查 snap alias'
|
||||
fi
|
||||
ok "LXD 安装完成"
|
||||
fi
|
||||
|
||||
info "配置 LXD..."
|
||||
snap set lxd lxcfs.flags="-l" 2>/dev/null
|
||||
snap set lxd daemon.debug=false 2>/dev/null
|
||||
snap restart lxd 2>/dev/null
|
||||
sleep 3
|
||||
ok "LXD 已配置(lxcfs legacy 模式 + 关闭调试)"
|
||||
}
|
||||
|
||||
setup_storage() {
|
||||
info "配置存储池..."
|
||||
|
||||
if /snap/bin/lxc storage show default &>/dev/null; then
|
||||
ok "存储池 default 已存在"
|
||||
/snap/bin/lxc storage list
|
||||
return 0
|
||||
fi
|
||||
|
||||
available_space=$(get_available_space)
|
||||
info "当前可用磁盘空间: ${available_space}GB"
|
||||
|
||||
while true; do
|
||||
reading "请选择存储后端 zfs/btrfs/lvm [zfs]:" storage_driver
|
||||
storage_driver=${storage_driver:-zfs}
|
||||
if [[ "$storage_driver" =~ ^(zfs|btrfs|lvm)$ ]]; then
|
||||
break
|
||||
else
|
||||
warn "请输入 zfs、btrfs 或 lvm"
|
||||
fi
|
||||
done
|
||||
|
||||
case "$storage_driver" in
|
||||
zfs)
|
||||
if ! command -v zpool &>/dev/null; then
|
||||
info "安装 ZFS..."
|
||||
if [[ "$SYSTEM" == "Ubuntu" ]]; then
|
||||
install_package zfsutils-linux
|
||||
else
|
||||
bash <(curl -sL https://raw.githubusercontent.com/xkatld/lxdapi-web-server/refs/heads/v2.0.0-main/build_zfs_on_debian.sh)
|
||||
fi
|
||||
fi
|
||||
info "配置 LXD 使用系统 ZFS..."
|
||||
snap set lxd zfs.external=true
|
||||
snap restart lxd
|
||||
sleep 3
|
||||
;;
|
||||
btrfs)
|
||||
install_package btrfs-progs
|
||||
;;
|
||||
lvm)
|
||||
install_package lvm2
|
||||
;;
|
||||
esac
|
||||
|
||||
reading "请输入存储池大小(GB) [${available_space}]:" pool_size
|
||||
pool_size=${pool_size:-$available_space}
|
||||
|
||||
info "创建 default 存储池 (${storage_driver}, ${pool_size}GB)..."
|
||||
/snap/bin/lxc storage create default ${storage_driver} size=${pool_size}GB
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
ok "存储池 default 创建成功"
|
||||
if ! /snap/bin/lxc profile device show default 2>/dev/null | grep -q "root"; then
|
||||
/snap/bin/lxc profile device add default root disk path=/ pool=default
|
||||
ok "存储池已添加到 default profile"
|
||||
fi
|
||||
else
|
||||
err "存储池创建失败"
|
||||
fi
|
||||
}
|
||||
|
||||
init_lxd_network() {
|
||||
if ! /snap/bin/lxc network show lxdbr0 &>/dev/null; then
|
||||
info "创建默认网络 lxdbr0..."
|
||||
/snap/bin/lxc network create lxdbr0
|
||||
ok "网络 lxdbr0 创建成功"
|
||||
else
|
||||
ok "网络 lxdbr0 已存在"
|
||||
fi
|
||||
|
||||
if ! /snap/bin/lxc profile device show default 2>/dev/null | grep -q "eth0"; then
|
||||
info "配置 default profile 网络设备..."
|
||||
/snap/bin/lxc profile device add default eth0 nic network=lxdbr0 name=eth0
|
||||
ok "网络设备已添加到 default profile"
|
||||
fi
|
||||
}
|
||||
|
||||
import_container_images() {
|
||||
bash <(curl -sL https://raw.githubusercontent.com/xkatld/lxdapi-web-server/refs/heads/v2.0.0-main/image_import.sh)
|
||||
systemctl enable nginx >/dev/null 2>&1
|
||||
systemctl start nginx >/dev/null 2>&1
|
||||
ok "nginx 已启动"
|
||||
}
|
||||
|
||||
deploy_lxdapi() {
|
||||
@@ -340,8 +102,6 @@ deploy_lxdapi() {
|
||||
fi
|
||||
done
|
||||
|
||||
info "获取最新版本..."
|
||||
|
||||
if [[ "$download_source" == "github" ]]; then
|
||||
latest_tag=$(curl -s https://api.github.com/repos/xkatld/lxdapi-web-server/releases/latest | grep '"tag_name"' | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p')
|
||||
base_url="https://github.com/xkatld/lxdapi-web-server/releases/download"
|
||||
@@ -354,9 +114,12 @@ deploy_lxdapi() {
|
||||
err "无法获取最新版本信息"
|
||||
fi
|
||||
|
||||
ok "最新版本: $latest_tag"
|
||||
info "最新版本: $latest_tag"
|
||||
reading "请输入安装版本 [$latest_tag]:" install_version
|
||||
install_version=${install_version:-$latest_tag}
|
||||
ok "安装版本: $install_version"
|
||||
|
||||
download_url="${base_url}/${latest_tag}/lxdapi-linux-${arch}.tar.gz"
|
||||
download_url="${base_url}/${install_version}/lxdapi-linux-${arch}.tar.gz"
|
||||
|
||||
info "下载 lxdapi..."
|
||||
info "下载地址: $download_url"
|
||||
@@ -393,11 +156,72 @@ configure_lxdapi() {
|
||||
ok "API密钥已生成: $api_hash"
|
||||
fi
|
||||
|
||||
reading "请输入流量采集间隔秒数 [30]:" traffic_interval
|
||||
traffic_interval=${traffic_interval:-30}
|
||||
reading "请输入管理员用户名 [admin]:" admin_user
|
||||
admin_user=${admin_user:-admin}
|
||||
|
||||
reading "请输入流量批量更新数量 [5]:" traffic_batch_size
|
||||
traffic_batch_size=${traffic_batch_size:-5}
|
||||
reading "请输入管理员密码 [随机生成]:" admin_pass
|
||||
if [ -z "$admin_pass" ]; then
|
||||
admin_pass=$(openssl rand -hex 8)
|
||||
ok "管理员密码已生成: $admin_pass"
|
||||
fi
|
||||
|
||||
session_secret=$(openssl rand -hex 16)
|
||||
|
||||
reading "请输入流量采集间隔秒数 [20]:" traffic_interval
|
||||
traffic_interval=${traffic_interval:-20}
|
||||
|
||||
reading "请输入流量批量更新数量 [10]:" traffic_batch_size
|
||||
traffic_batch_size=${traffic_batch_size:-10}
|
||||
|
||||
reading "请输入任务自动清理天数 [7]:" auto_cleanup_days
|
||||
auto_cleanup_days=${auto_cleanup_days:-7}
|
||||
|
||||
while true; do
|
||||
reading "请选择任务队列后端 memory/redis [memory]:" task_backend
|
||||
task_backend=${task_backend:-memory}
|
||||
if [[ "$task_backend" =~ ^(memory|redis)$ ]]; then
|
||||
break
|
||||
else
|
||||
warn "请输入 memory 或 redis"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$task_backend" == "redis" ]]; then
|
||||
while true; do
|
||||
reading "使用本地安装还是远程配置?local/remote [local]:" redis_location
|
||||
redis_location=${redis_location:-local}
|
||||
if [[ "$redis_location" =~ ^(local|remote)$ ]]; then
|
||||
break
|
||||
else
|
||||
warn "请输入 local 或 remote"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$redis_location" == "local" ]]; then
|
||||
info "安装 Redis..."
|
||||
apt-get install -y redis-server >/dev/null 2>&1
|
||||
systemctl start redis-server
|
||||
systemctl enable redis-server
|
||||
|
||||
redis_host="localhost"
|
||||
redis_port="6379"
|
||||
redis_password=""
|
||||
redis_db="0"
|
||||
ok "Redis 已安装"
|
||||
else
|
||||
reading "请输入 Redis 主机地址:" redis_host
|
||||
reading "请输入 Redis 端口 [6379]:" redis_port
|
||||
redis_port=${redis_port:-6379}
|
||||
reading "请输入 Redis 密码 [留空表示无密码]:" redis_password
|
||||
reading "请输入 Redis 数据库编号 [0]:" redis_db
|
||||
redis_db=${redis_db:-0}
|
||||
fi
|
||||
else
|
||||
redis_host="localhost"
|
||||
redis_port="6379"
|
||||
redis_password=""
|
||||
redis_db="0"
|
||||
fi
|
||||
|
||||
while true; do
|
||||
reading "请选择数据库类型 sqlite/mysql/postgres [sqlite]:" db_type
|
||||
@@ -422,9 +246,9 @@ configure_lxdapi() {
|
||||
|
||||
if [[ "$mysql_location" == "local" ]]; then
|
||||
info "安装 MariaDB..."
|
||||
install_package mariadb-server
|
||||
service_manager start mariadb
|
||||
service_manager enable mariadb
|
||||
apt-get install -y mariadb-server >/dev/null 2>&1
|
||||
systemctl start mariadb
|
||||
systemctl enable mariadb
|
||||
|
||||
mysql_host="localhost"
|
||||
mysql_port="3306"
|
||||
@@ -451,12 +275,6 @@ EOF
|
||||
reading "请输入 MySQL 数据库名:" mysql_database
|
||||
fi
|
||||
|
||||
sed -i "s|__MYSQL_HOST__|$mysql_host|g" "$config_file"
|
||||
sed -i "s|__MYSQL_PORT__|$mysql_port|g" "$config_file"
|
||||
sed -i "s|__MYSQL_USER__|$mysql_user|g" "$config_file"
|
||||
sed -i "s|__MYSQL_PASSWORD__|$mysql_password|g" "$config_file"
|
||||
sed -i "s|__MYSQL_DATABASE__|$mysql_database|g" "$config_file"
|
||||
|
||||
elif [[ "$db_type" == "postgres" ]]; then
|
||||
while true; do
|
||||
reading "使用本地安装还是远程配置?local/remote [local]:" postgres_location
|
||||
@@ -470,9 +288,9 @@ EOF
|
||||
|
||||
if [[ "$postgres_location" == "local" ]]; then
|
||||
info "安装 PostgreSQL..."
|
||||
install_package postgresql
|
||||
service_manager start postgresql
|
||||
service_manager enable postgresql
|
||||
apt-get install -y postgresql >/dev/null 2>&1
|
||||
systemctl start postgresql
|
||||
systemctl enable postgresql
|
||||
|
||||
postgres_host="localhost"
|
||||
postgres_port="5432"
|
||||
@@ -500,88 +318,54 @@ EOF
|
||||
reading "请输入 PostgreSQL SSL模式 [disable]:" postgres_sslmode
|
||||
postgres_sslmode=${postgres_sslmode:-disable}
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
info "写入配置文件..."
|
||||
sed -i "s|__SERVER_PORT__|$server_port|g" "$config_file"
|
||||
sed -i "s|__API_HASH__|$api_hash|g" "$config_file"
|
||||
sed -i "s|__ADMIN_USER__|$admin_user|g" "$config_file"
|
||||
sed -i "s|__ADMIN_PASS__|$admin_pass|g" "$config_file"
|
||||
sed -i "s|__SESSION_SECRET__|$session_secret|g" "$config_file"
|
||||
sed -i "s|__TRAFFIC_INTERVAL__|$traffic_interval|g" "$config_file"
|
||||
sed -i "s|__TRAFFIC_BATCH_SIZE__|$traffic_batch_size|g" "$config_file"
|
||||
sed -i "s|__AUTO_CLEANUP_DAYS__|$auto_cleanup_days|g" "$config_file"
|
||||
sed -i "s|__TASK_BACKEND__|$task_backend|g" "$config_file"
|
||||
sed -i "s|__REDIS_HOST__|$redis_host|g" "$config_file"
|
||||
sed -i "s|__REDIS_PORT__|$redis_port|g" "$config_file"
|
||||
sed -i "s|__REDIS_PASSWORD__|$redis_password|g" "$config_file"
|
||||
sed -i "s|__REDIS_DB__|$redis_db|g" "$config_file"
|
||||
sed -i "s|__DB_TYPE__|$db_type|g" "$config_file"
|
||||
|
||||
if [[ "$db_type" == "mysql" ]]; then
|
||||
sed -i "s|__MYSQL_HOST__|$mysql_host|g" "$config_file"
|
||||
sed -i "s|__MYSQL_PORT__|$mysql_port|g" "$config_file"
|
||||
sed -i "s|__MYSQL_USER__|$mysql_user|g" "$config_file"
|
||||
sed -i "s|__MYSQL_PASSWORD__|$mysql_password|g" "$config_file"
|
||||
sed -i "s|__MYSQL_DATABASE__|$mysql_database|g" "$config_file"
|
||||
else
|
||||
sed -i "s|__MYSQL_HOST__|localhost|g" "$config_file"
|
||||
sed -i "s|__MYSQL_PORT__|3306|g" "$config_file"
|
||||
sed -i "s|__MYSQL_USER__|root|g" "$config_file"
|
||||
sed -i "s|__MYSQL_PASSWORD__||g" "$config_file"
|
||||
sed -i "s|__MYSQL_DATABASE__|lxdapi|g" "$config_file"
|
||||
fi
|
||||
|
||||
if [[ "$db_type" == "postgres" ]]; then
|
||||
sed -i "s|__POSTGRES_HOST__|$postgres_host|g" "$config_file"
|
||||
sed -i "s|__POSTGRES_PORT__|$postgres_port|g" "$config_file"
|
||||
sed -i "s|__POSTGRES_USER__|$postgres_user|g" "$config_file"
|
||||
sed -i "s|__POSTGRES_PASSWORD__|$postgres_password|g" "$config_file"
|
||||
sed -i "s|__POSTGRES_DATABASE__|$postgres_database|g" "$config_file"
|
||||
sed -i "s|__POSTGRES_SSLMODE__|$postgres_sslmode|g" "$config_file"
|
||||
else
|
||||
sed -i "s|__POSTGRES_HOST__|localhost|g" "$config_file"
|
||||
sed -i "s|__POSTGRES_PORT__|5432|g" "$config_file"
|
||||
sed -i "s|__POSTGRES_USER__|postgres|g" "$config_file"
|
||||
sed -i "s|__POSTGRES_PASSWORD__||g" "$config_file"
|
||||
sed -i "s|__POSTGRES_DATABASE__|lxdapi|g" "$config_file"
|
||||
sed -i "s|__POSTGRES_SSLMODE__|disable|g" "$config_file"
|
||||
fi
|
||||
|
||||
while true; do
|
||||
reading "请选择任务队列后端 memory/redis [memory]:" task_backend
|
||||
task_backend=${task_backend:-memory}
|
||||
if [[ "$task_backend" =~ ^(memory|redis)$ ]]; then
|
||||
break
|
||||
else
|
||||
warn "请输入 memory 或 redis"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$task_backend" == "redis" ]]; then
|
||||
info "安装 Redis..."
|
||||
install_package redis-server
|
||||
service_manager start redis-server
|
||||
service_manager enable redis-server
|
||||
|
||||
redis_host="localhost"
|
||||
redis_port="6379"
|
||||
redis_password=""
|
||||
redis_db="0"
|
||||
|
||||
ok "Redis 已安装并启动"
|
||||
|
||||
sed -i "s|__REDIS_HOST__|$redis_host|g" "$config_file"
|
||||
sed -i "s|__REDIS_PORT__|$redis_port|g" "$config_file"
|
||||
sed -i "s|__REDIS_PASSWORD__|$redis_password|g" "$config_file"
|
||||
sed -i "s|__REDIS_DB__|$redis_db|g" "$config_file"
|
||||
fi
|
||||
|
||||
reading "请输入管理员用户名 [admin]:" admin_user
|
||||
admin_user=${admin_user:-admin}
|
||||
|
||||
reading "请输入管理员密码 [随机生成]:" admin_pass
|
||||
if [ -z "$admin_pass" ]; then
|
||||
admin_pass=$(openssl rand -hex 4)
|
||||
ok "管理员密码已生成: $admin_pass"
|
||||
fi
|
||||
|
||||
reading "请输入Session密钥 [随机生成]:" session_secret
|
||||
if [ -z "$session_secret" ]; then
|
||||
session_secret=$(openssl rand -hex 16)
|
||||
ok "Session密钥已生成: $session_secret"
|
||||
fi
|
||||
|
||||
info "写入配置文件..."
|
||||
sed -i "s|__SERVER_PORT__|$server_port|g" "$config_file"
|
||||
sed -i "s|__API_HASH__|$api_hash|g" "$config_file"
|
||||
sed -i "s|__TRAFFIC_INTERVAL__|$traffic_interval|g" "$config_file"
|
||||
sed -i "s|__TRAFFIC_BATCH_SIZE__|$traffic_batch_size|g" "$config_file"
|
||||
sed -i "s|__DB_TYPE__|$db_type|g" "$config_file"
|
||||
sed -i "s|__TASK_BACKEND__|$task_backend|g" "$config_file"
|
||||
sed -i "s|__ADMIN_USER__|$admin_user|g" "$config_file"
|
||||
sed -i "s|__ADMIN_PASS__|$admin_pass|g" "$config_file"
|
||||
sed -i "s|__SESSION_SECRET__|$session_secret|g" "$config_file"
|
||||
|
||||
sed -i "s|__MYSQL_HOST__|localhost|g" "$config_file"
|
||||
sed -i "s|__MYSQL_PORT__|3306|g" "$config_file"
|
||||
sed -i "s|__MYSQL_USER__|lxdapi|g" "$config_file"
|
||||
sed -i "s|__MYSQL_PASSWORD__|password|g" "$config_file"
|
||||
sed -i "s|__MYSQL_DATABASE__|lxdapi|g" "$config_file"
|
||||
|
||||
sed -i "s|__POSTGRES_HOST__|localhost|g" "$config_file"
|
||||
sed -i "s|__POSTGRES_PORT__|5432|g" "$config_file"
|
||||
sed -i "s|__POSTGRES_USER__|lxdapi|g" "$config_file"
|
||||
sed -i "s|__POSTGRES_PASSWORD__|password|g" "$config_file"
|
||||
sed -i "s|__POSTGRES_DATABASE__|lxdapi|g" "$config_file"
|
||||
sed -i "s|__POSTGRES_SSLMODE__|disable|g" "$config_file"
|
||||
|
||||
sed -i "s|__REDIS_HOST__|localhost|g" "$config_file"
|
||||
sed -i "s|__REDIS_PORT__|6379|g" "$config_file"
|
||||
sed -i "s|__REDIS_PASSWORD__||g" "$config_file"
|
||||
sed -i "s|__REDIS_DB__|0|g" "$config_file"
|
||||
|
||||
ok "配置文件已更新"
|
||||
}
|
||||
|
||||
@@ -635,26 +419,21 @@ EOF
|
||||
|
||||
ok "服务文件已创建: $service_file"
|
||||
|
||||
info "重载 systemd 配置..."
|
||||
systemctl daemon-reload
|
||||
|
||||
info "启用开机自启..."
|
||||
systemctl enable lxdapi
|
||||
|
||||
info "启动 lxdapi 服务..."
|
||||
systemctl start lxdapi
|
||||
|
||||
sleep 2
|
||||
info "等待服务启动..."
|
||||
for i in {1..10}; do
|
||||
printf "\r[%-10s] %d/10s" "$(printf '#%.0s' $(seq 1 $i))" "$i"
|
||||
sleep 1
|
||||
done
|
||||
echo
|
||||
|
||||
if systemctl is-active --quiet lxdapi; then
|
||||
ok "lxdapi 服务已启动"
|
||||
echo
|
||||
info "===== 服务状态 ====="
|
||||
systemctl status lxdapi --no-pager | head -10
|
||||
else
|
||||
warn "lxdapi 服务启动失败"
|
||||
echo
|
||||
info "===== 错误日志 ====="
|
||||
journalctl -u lxdapi -n 20 --no-pager
|
||||
fi
|
||||
}
|
||||
@@ -666,94 +445,66 @@ main() {
|
||||
echo " by Github-xkatld"
|
||||
echo "========================================"
|
||||
echo
|
||||
print_step "1" "4" "初始化环境"
|
||||
reading "是否执行环境初始化?(y/n) [y]:" step1_confirm
|
||||
|
||||
echo "======== 步骤 1/5: 基础软件包安装 ========"
|
||||
reading "是否安装基础软件包?(y/n) [y]:" step1_confirm
|
||||
step1_confirm=${step1_confirm:-y}
|
||||
if [[ "$step1_confirm" =~ ^[yY]$ ]]; then
|
||||
set_locale
|
||||
install_base_packages
|
||||
ok "环境初始化完成"
|
||||
ok "基础软件包安装完成"
|
||||
else
|
||||
info "已跳过环境初始化"
|
||||
info "已跳过基础软件包安装"
|
||||
fi
|
||||
echo
|
||||
|
||||
print_step "2" "4" "安装 LXD"
|
||||
reading "是否执行 LXD 安装?(y/n) [y]:" step2_confirm
|
||||
echo "======== 步骤 2/5: 下载 ========"
|
||||
reading "是否下载 lxdapi?(y/n) [y]:" step2_confirm
|
||||
step2_confirm=${step2_confirm:-y}
|
||||
if [[ "$step2_confirm" =~ ^[yY]$ ]]; then
|
||||
install_lxd
|
||||
init_lxd_network
|
||||
ok "LXD 安装完成"
|
||||
deploy_lxdapi
|
||||
ok "下载完成"
|
||||
else
|
||||
info "已跳过 LXD 安装"
|
||||
info "已跳过下载"
|
||||
fi
|
||||
echo
|
||||
|
||||
print_step "3" "4" "配置存储资源"
|
||||
reading "是否执行存储配置?(y/n) [y]:" step3_confirm
|
||||
echo "======== 步骤 3/5: 配置 ========"
|
||||
reading "是否配置 lxdapi?(y/n) [y]:" step3_confirm
|
||||
step3_confirm=${step3_confirm:-y}
|
||||
if [[ "$step3_confirm" =~ ^[yY]$ ]]; then
|
||||
setup_storage
|
||||
ok "存储配置完成"
|
||||
else
|
||||
info "已跳过存储配置"
|
||||
fi
|
||||
|
||||
print_step "4" "4" "部署 lxdapi"
|
||||
reading "是否执行 lxdapi 部署?(y/n) [y]:" step5_confirm
|
||||
step5_confirm=${step5_confirm:-y}
|
||||
if [[ "$step5_confirm" =~ ^[yY]$ ]]; then
|
||||
deploy_lxdapi
|
||||
configure_lxdapi
|
||||
setup_lxdapi_service
|
||||
ok "lxdapi 部署完成"
|
||||
ok "配置完成"
|
||||
else
|
||||
info "已跳过 lxdapi 部署"
|
||||
info "已跳过配置"
|
||||
fi
|
||||
echo
|
||||
|
||||
echo "======== 步骤 4/5: 启动服务 ========"
|
||||
reading "是否启动 lxdapi 服务?(y/n) [y]:" step4_confirm
|
||||
step4_confirm=${step4_confirm:-y}
|
||||
if [[ "$step4_confirm" =~ ^[yY]$ ]]; then
|
||||
setup_lxdapi_service
|
||||
ok "服务已启动"
|
||||
else
|
||||
info "已跳过服务启动"
|
||||
fi
|
||||
echo
|
||||
|
||||
echo "======== 步骤 5/5: 完成 ========"
|
||||
echo
|
||||
echo "========================================"
|
||||
echo " lxdapi 安装完成"
|
||||
echo " LXDAPI 安装完成"
|
||||
echo "========================================"
|
||||
echo
|
||||
|
||||
info "LXD 版本: $(lxd --version)"
|
||||
info "LXC 版本: $(lxc --version)"
|
||||
echo
|
||||
|
||||
info "===== 1. 网络配置 ====="
|
||||
lxc network list
|
||||
echo
|
||||
|
||||
info "===== 2. 存储配置 ====="
|
||||
lxc storage list
|
||||
echo
|
||||
|
||||
info "===== 3. 后端配置 ====="
|
||||
info "服务端口: $server_port"
|
||||
info "API密钥: $api_hash"
|
||||
info "流量间隔: $traffic_interval 秒"
|
||||
info "批量大小: $traffic_batch_size"
|
||||
info "数据库: $db_type"
|
||||
if [[ "$db_type" == "mysql" ]]; then
|
||||
info "MySQL: $mysql_user@$mysql_host:$mysql_port/$mysql_database"
|
||||
info "MySQL密码: $mysql_password"
|
||||
elif [[ "$db_type" == "postgres" ]]; then
|
||||
info "PostgreSQL: $postgres_user@$postgres_host:$postgres_port/$postgres_database"
|
||||
info "PostgreSQL密码: $postgres_password"
|
||||
fi
|
||||
info "任务队列: $task_backend"
|
||||
if [[ "$task_backend" == "redis" ]]; then
|
||||
info "Redis: localhost:6379"
|
||||
fi
|
||||
info "管理员: $admin_user"
|
||||
info "管理员用户: $admin_user"
|
||||
info "管理员密码: $admin_pass"
|
||||
info "Session密钥: $session_secret"
|
||||
info "数据库类型: $db_type"
|
||||
info "任务队列: $task_backend"
|
||||
info "流量采集间隔: ${traffic_interval}s"
|
||||
echo
|
||||
|
||||
info "===== 5. lxdapi 服务状态 ====="
|
||||
info "等待服务启动..."
|
||||
sleep 5
|
||||
systemctl status lxdapi --no-pager -l
|
||||
systemctl status lxdapi --no-pager | head -5
|
||||
}
|
||||
|
||||
main
|
||||
112
Shell/lxdapi_tool.sh
Normal file
112
Shell/lxdapi_tool.sh
Normal file
@@ -0,0 +1,112 @@
|
||||
#!/bin/bash
|
||||
|
||||
INSTALL_DIR="/opt/lxdapi"
|
||||
SERVICE_NAME="lxdapi"
|
||||
SCRIPT_PATH="/usr/local/bin/lxdapi"
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
NC='\033[0m'
|
||||
|
||||
if [ "$(realpath "$0")" != "$SCRIPT_PATH" ]; then
|
||||
cp "$0" "$SCRIPT_PATH"
|
||||
chmod +x "$SCRIPT_PATH"
|
||||
echo "lxdapi 命令已安装"
|
||||
echo "用法: lxdapi 或 lxdapi {start|stop|restart|status|config|machine-id}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
wait_progress() {
|
||||
echo "等待服务响应..."
|
||||
for i in {1..10}; do
|
||||
printf "\r[%-10s] %d/10s" "$(printf '#%.0s' $(seq 1 $i))" "$i"
|
||||
sleep 1
|
||||
done
|
||||
echo
|
||||
}
|
||||
|
||||
do_start() {
|
||||
systemctl start $SERVICE_NAME
|
||||
echo "lxdapi 已启动"
|
||||
wait_progress
|
||||
systemctl status $SERVICE_NAME --no-pager | head -5
|
||||
}
|
||||
|
||||
do_stop() {
|
||||
systemctl stop $SERVICE_NAME
|
||||
echo "lxdapi 已停止"
|
||||
sleep 1
|
||||
systemctl status $SERVICE_NAME --no-pager | head -5
|
||||
}
|
||||
|
||||
do_restart() {
|
||||
systemctl restart $SERVICE_NAME
|
||||
echo "lxdapi 已重启"
|
||||
wait_progress
|
||||
systemctl status $SERVICE_NAME --no-pager | head -5
|
||||
}
|
||||
|
||||
do_status() {
|
||||
systemctl status $SERVICE_NAME --no-pager | head -5
|
||||
echo
|
||||
echo "===== 最近日志 ====="
|
||||
journalctl -u $SERVICE_NAME -n 10 --no-pager
|
||||
}
|
||||
|
||||
do_config() {
|
||||
CONFIG_FILE="$INSTALL_DIR/configs/config.yaml"
|
||||
PORT=$(grep "port:" "$CONFIG_FILE" | head -1 | awk '{print $2}')
|
||||
API_KEY=$(grep "api_key:" "$CONFIG_FILE" | awk -F'"' '{print $2}')
|
||||
echo "端口: $PORT"
|
||||
echo "API密钥: $API_KEY"
|
||||
}
|
||||
|
||||
do_machine_id() {
|
||||
ARCH=$(uname -m)
|
||||
case $ARCH in
|
||||
x86_64) ARCH="amd64" ;;
|
||||
aarch64|arm64) ARCH="arm64" ;;
|
||||
esac
|
||||
$INSTALL_DIR/lxdapi-$ARCH --machine-id
|
||||
}
|
||||
|
||||
show_menu() {
|
||||
echo
|
||||
echo "================================"
|
||||
echo " LXDAPI 管理工具"
|
||||
echo "================================"
|
||||
echo "1. 启动"
|
||||
echo "2. 停止"
|
||||
echo "3. 重启"
|
||||
echo "4. 状态"
|
||||
echo "5. 配置"
|
||||
echo "6. 机器码"
|
||||
echo "0. 退出"
|
||||
echo "================================"
|
||||
read -rp "$(echo -e "${GREEN}请选择 [0-6]: ${NC}")" choice
|
||||
|
||||
case "$choice" in
|
||||
1) do_start ;;
|
||||
2) do_stop ;;
|
||||
3) do_restart ;;
|
||||
4) do_status ;;
|
||||
5) do_config ;;
|
||||
6) do_machine_id ;;
|
||||
0) exit 0 ;;
|
||||
*) echo "无效选择" ;;
|
||||
esac
|
||||
show_menu
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start) do_start ;;
|
||||
stop) do_stop ;;
|
||||
restart) do_restart ;;
|
||||
status) do_status ;;
|
||||
config) do_config ;;
|
||||
machine-id) do_machine_id ;;
|
||||
"") show_menu ;;
|
||||
*)
|
||||
echo "用法: lxdapi 或 lxdapi {start|stop|restart|status|config|machine-id}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -20,25 +20,10 @@ SERVICE_NAME="lxdapi"
|
||||
check_environment() {
|
||||
info "检测运行环境..."
|
||||
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
err "请使用 root 用户运行此脚本"
|
||||
fi
|
||||
|
||||
if [ ! -d "$INSTALL_DIR" ]; then
|
||||
err "未检测到 lxdapi 安装目录: $INSTALL_DIR"
|
||||
fi
|
||||
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
err "未检测到配置文件: $CONFIG_FILE"
|
||||
fi
|
||||
|
||||
if ! command -v nft &>/dev/null; then
|
||||
info "安装 nftables..."
|
||||
apt-get update >/dev/null 2>&1
|
||||
apt-get install -y nftables >/dev/null 2>&1
|
||||
ok "nftables 已安装"
|
||||
fi
|
||||
|
||||
ok "环境检测通过"
|
||||
}
|
||||
|
||||
@@ -62,9 +47,9 @@ detect_arch() {
|
||||
|
||||
get_current_version() {
|
||||
if [ -f "$INSTALL_DIR/lxdapi-$ARCH" ]; then
|
||||
CURRENT_VERSION=$("$INSTALL_DIR/lxdapi-$ARCH" --version 2>/dev/null || echo "未知")
|
||||
CURRENT_VERSION=$(stat -c %y "$INSTALL_DIR/lxdapi-$ARCH" 2>/dev/null | cut -d' ' -f1)
|
||||
else
|
||||
CURRENT_VERSION="未知"
|
||||
CURRENT_VERSION="未安装"
|
||||
fi
|
||||
info "当前版本: $CURRENT_VERSION"
|
||||
}
|
||||
@@ -77,7 +62,10 @@ get_latest_version() {
|
||||
err "无法获取最新版本信息,请检查网络连接"
|
||||
fi
|
||||
|
||||
ok "最新版本: $LATEST_VERSION"
|
||||
info "最新版本: $LATEST_VERSION"
|
||||
read -rp "$(echo -e "${GREEN}请输入更新版本 [$LATEST_VERSION]: ${NC}")" UPDATE_VERSION
|
||||
UPDATE_VERSION=${UPDATE_VERSION:-$LATEST_VERSION}
|
||||
ok "更新版本: $UPDATE_VERSION"
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
@@ -121,53 +109,10 @@ backup_files() {
|
||||
fi
|
||||
}
|
||||
|
||||
fix_service_file() {
|
||||
info "检查服务文件..."
|
||||
|
||||
SERVICE_FILE="/etc/systemd/system/lxdapi.service"
|
||||
|
||||
if [ ! -f "$SERVICE_FILE" ]; then
|
||||
warn "服务文件不存在,跳过修复"
|
||||
return
|
||||
fi
|
||||
|
||||
if grep -q "Environment=\"PATH=" "$SERVICE_FILE"; then
|
||||
ok "服务文件PATH配置正常"
|
||||
return
|
||||
fi
|
||||
|
||||
info "修复服务文件PATH配置..."
|
||||
|
||||
EXEC_BIN="$INSTALL_DIR/lxdapi-$ARCH"
|
||||
|
||||
cat > "$SERVICE_FILE" << EOF
|
||||
[Unit]
|
||||
Description=LXD API Server
|
||||
After=network.target lxd.service
|
||||
Wants=lxd.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/opt/lxdapi
|
||||
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin"
|
||||
ExecStart=$EXEC_BIN
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
ok "服务文件已修复"
|
||||
}
|
||||
|
||||
download_latest() {
|
||||
info "下载最新版本..."
|
||||
info "下载版本 $UPDATE_VERSION..."
|
||||
|
||||
DOWNLOAD_URL="https://github.com/xkatld/lxdapi-web-server/releases/download/${LATEST_VERSION}/lxdapi-linux-${ARCH}.tar.gz"
|
||||
DOWNLOAD_URL="https://github.com/xkatld/lxdapi-web-server/releases/download/${UPDATE_VERSION}/lxdapi-linux-${ARCH}.tar.gz"
|
||||
info "下载地址: $DOWNLOAD_URL"
|
||||
|
||||
TEMP_FILE=$(mktemp)
|
||||
@@ -208,7 +153,12 @@ start_service() {
|
||||
systemctl daemon-reload
|
||||
systemctl start $SERVICE_NAME
|
||||
|
||||
sleep 3
|
||||
info "等待服务启动..."
|
||||
for i in {1..10}; do
|
||||
printf "\r[%-10s] %d/10s" "$(printf '#%.0s' $(seq 1 $i))" "$i"
|
||||
sleep 1
|
||||
done
|
||||
echo
|
||||
|
||||
if systemctl is-active --quiet $SERVICE_NAME; then
|
||||
ok "服务已启动"
|
||||
@@ -226,7 +176,7 @@ show_result() {
|
||||
echo "========================================"
|
||||
echo
|
||||
info "更新前版本: $CURRENT_VERSION"
|
||||
info "更新后版本: $LATEST_VERSION"
|
||||
info "更新后版本: $UPDATE_VERSION"
|
||||
info "备份目录: $BACKUP_PATH"
|
||||
echo
|
||||
info "===== 服务状态 ====="
|
||||
@@ -275,7 +225,6 @@ main() {
|
||||
echo
|
||||
stop_service
|
||||
backup_files
|
||||
fix_service_file
|
||||
|
||||
if download_latest; then
|
||||
start_service
|
||||
@@ -47,7 +47,7 @@ install_zfs() {
|
||||
return 1
|
||||
fi
|
||||
info "开始编译安装 ZFS..."
|
||||
bash <(curl -sL https://raw.githubusercontent.com/xkatld/lxdapi-web-server/refs/heads/v2.0.0-main/build_zfs_on_debian.sh) || return 1
|
||||
bash <(curl -sL https://raw.githubusercontent.com/xkatld/lxdapi-web-server/refs/heads/v2.1.0-vpsm.link/Shell/debian_zfs.sh) || return 1
|
||||
else
|
||||
info "安装 ZFS..."
|
||||
apt-get update -qq && apt-get install -y zfsutils-linux -qq
|
||||
@@ -329,4 +329,5 @@ main_menu() {
|
||||
|
||||
check_root
|
||||
check_lxd
|
||||
install_package bc
|
||||
main_menu
|
||||
@@ -1,68 +0,0 @@
|
||||
{# ========== 汉化邮箱插件 by xkatld 开始 ========== #}
|
||||
{# @link https://github.com/xkatld/FOSSBilling-Patch #}
|
||||
{# @version v1.0.0 #}
|
||||
|
||||
{% block subject %}[{{ guest.system_company.name }}] {{ order.title }} 已激活{% endblock %}
|
||||
{% block content %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style type="text/css">
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; font-size: 14px; line-height: 1.6; color: #333; background-color: #f4f6f9; }
|
||||
.email-wrapper { max-width: 600px; margin: 0 auto; background-color: #ffffff; }
|
||||
.email-header { background: linear-gradient(135deg, #206bc4 0%, #4299e1 100%); padding: 30px 40px; text-align: center; }
|
||||
.email-header h1 { color: #ffffff; font-size: 24px; font-weight: 600; margin: 0; }
|
||||
.email-header .subtitle { color: rgba(255,255,255,0.9); font-size: 14px; margin-top: 5px; }
|
||||
.email-body { padding: 40px; background-color: #ffffff; }
|
||||
.email-body h2 { color: #206bc4; font-size: 20px; font-weight: 600; margin-bottom: 20px; border-bottom: 2px solid #e9ecef; padding-bottom: 10px; }
|
||||
.email-body p { margin-bottom: 15px; color: #495057; }
|
||||
.email-body .highlight { background-color: #e7f3ff; border-left: 4px solid #206bc4; padding: 15px 20px; margin: 20px 0; border-radius: 0 4px 4px 0; }
|
||||
.email-body ul { margin: 15px 0; padding-left: 0; list-style: none; }
|
||||
.email-body ul li { padding: 10px 15px; border-bottom: 1px solid #e9ecef; background-color: #f8f9fa; }
|
||||
.email-body ul li:first-child { border-radius: 4px 4px 0 0; }
|
||||
.email-body ul li:last-child { border-bottom: none; border-radius: 0 0 4px 4px; }
|
||||
.email-body ul li strong { color: #206bc4; min-width: 100px; display: inline-block; }
|
||||
.btn { display: inline-block; padding: 12px 30px; background: linear-gradient(135deg, #206bc4 0%, #4299e1 100%); color: #ffffff !important; text-decoration: none; border-radius: 6px; font-weight: 500; margin: 10px 5px 10px 0; }
|
||||
.email-footer { background-color: #f8f9fa; padding: 30px 40px; text-align: center; border-top: 1px solid #e9ecef; }
|
||||
.email-footer .signature { color: #6c757d; font-size: 13px; line-height: 1.8; }
|
||||
.email-footer .company-info { margin-top: 15px; padding-top: 15px; border-top: 1px solid #e9ecef; color: #adb5bd; font-size: 12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="email-wrapper">
|
||||
<div class="email-header">
|
||||
<h1>{{ guest.system_company.name }}</h1>
|
||||
<div class="subtitle">LXD容器已激活</div>
|
||||
</div>
|
||||
<div class="email-body">
|
||||
<h2>容器激活成功</h2>
|
||||
<p>您好 {{ c.first_name }} {{ c.last_name }},</p>
|
||||
|
||||
<div class="highlight">
|
||||
<p><strong>恭喜!</strong>您的LXD容器已成功激活,可以开始使用了。</p>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<li><strong>容器名称:</strong> {{ service.container_name }}</li>
|
||||
<li><strong>服务名称:</strong> {{ order.title }}</li>
|
||||
<li><strong>激活时间:</strong> {{ order.activated_at|format_date }}</li>
|
||||
{% if order.expires_at %}<li><strong>到期时间:</strong> {{ order.expires_at|format_date }}</li>{% endif %}
|
||||
</ul>
|
||||
|
||||
<p style="text-align: center; margin: 30px 0;">
|
||||
<a href="{{ 'order/service/manage'|link }}/{{ order.id }}" class="btn">管理容器</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="email-footer">
|
||||
<div class="signature">{{ guest.system_company.signature }}</div>
|
||||
<div class="company-info">此邮件由 {{ guest.system_company.name }} 系统自动发送</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
|
||||
{# ========== 汉化邮箱插件 by xkatld 结束 ========== #}
|
||||
@@ -1,68 +0,0 @@
|
||||
{# ========== 汉化邮箱插件 by xkatld 开始 ========== #}
|
||||
{# @link https://github.com/xkatld/FOSSBilling-Patch #}
|
||||
{# @version v1.0.0 #}
|
||||
|
||||
{% block subject %}[{{ guest.system_company.name }}] {{ order.title }} 已取消{% endblock %}
|
||||
{% block content %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style type="text/css">
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; font-size: 14px; line-height: 1.6; color: #333; background-color: #f4f6f9; }
|
||||
.email-wrapper { max-width: 600px; margin: 0 auto; background-color: #ffffff; }
|
||||
.email-header { background: linear-gradient(135deg, #206bc4 0%, #4299e1 100%); padding: 30px 40px; text-align: center; }
|
||||
.email-header h1 { color: #ffffff; font-size: 24px; font-weight: 600; margin: 0; }
|
||||
.email-header .subtitle { color: rgba(255,255,255,0.9); font-size: 14px; margin-top: 5px; }
|
||||
.email-body { padding: 40px; background-color: #ffffff; }
|
||||
.email-body h2 { color: #206bc4; font-size: 20px; font-weight: 600; margin-bottom: 20px; border-bottom: 2px solid #e9ecef; padding-bottom: 10px; }
|
||||
.email-body p { margin-bottom: 15px; color: #495057; }
|
||||
.email-body .highlight { background-color: #e7f3ff; border-left: 4px solid #206bc4; padding: 15px 20px; margin: 20px 0; border-radius: 0 4px 4px 0; }
|
||||
.email-body ul { margin: 15px 0; padding-left: 0; list-style: none; }
|
||||
.email-body ul li { padding: 10px 15px; border-bottom: 1px solid #e9ecef; background-color: #f8f9fa; }
|
||||
.email-body ul li:first-child { border-radius: 4px 4px 0 0; }
|
||||
.email-body ul li:last-child { border-bottom: none; border-radius: 0 0 4px 4px; }
|
||||
.email-body ul li strong { color: #206bc4; min-width: 100px; display: inline-block; }
|
||||
.btn { display: inline-block; padding: 12px 30px; background: linear-gradient(135deg, #206bc4 0%, #4299e1 100%); color: #ffffff !important; text-decoration: none; border-radius: 6px; font-weight: 500; margin: 10px 5px 10px 0; }
|
||||
.email-footer { background-color: #f8f9fa; padding: 30px 40px; text-align: center; border-top: 1px solid #e9ecef; }
|
||||
.email-footer .signature { color: #6c757d; font-size: 13px; line-height: 1.8; }
|
||||
.email-footer .company-info { margin-top: 15px; padding-top: 15px; border-top: 1px solid #e9ecef; color: #adb5bd; font-size: 12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="email-wrapper">
|
||||
<div class="email-header">
|
||||
<h1>{{ guest.system_company.name }}</h1>
|
||||
<div class="subtitle">LXD容器已取消</div>
|
||||
</div>
|
||||
<div class="email-body">
|
||||
<h2>服务已取消</h2>
|
||||
<p>您好 {{ c.first_name }} {{ c.last_name }},</p>
|
||||
|
||||
<div class="highlight">
|
||||
<p>您的LXD容器服务已被取消。容器数据已被删除。</p>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<li><strong>容器名称:</strong> {{ service.container_name }}</li>
|
||||
<li><strong>服务名称:</strong> {{ order.title }}</li>
|
||||
</ul>
|
||||
|
||||
<p>如有任何疑问,请联系我们的客服团队。感谢您的使用!</p>
|
||||
|
||||
<p style="text-align: center; margin: 30px 0;">
|
||||
<a href="{{ 'login'|link({'email': c.email}) }}" class="btn">登录客户中心</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="email-footer">
|
||||
<div class="signature">{{ guest.system_company.signature }}</div>
|
||||
<div class="company-info">此邮件由 {{ guest.system_company.name }} 系统自动发送</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
|
||||
{# ========== 汉化邮箱插件 by xkatld 结束 ========== #}
|
||||
@@ -1,68 +0,0 @@
|
||||
{# ========== 汉化邮箱插件 by xkatld 开始 ========== #}
|
||||
{# @link https://github.com/xkatld/FOSSBilling-Patch #}
|
||||
{# @version v1.0.0 #}
|
||||
|
||||
{% block subject %}[{{ guest.system_company.name }}] {{ order.title }} 已暂停{% endblock %}
|
||||
{% block content %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style type="text/css">
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; font-size: 14px; line-height: 1.6; color: #333; background-color: #f4f6f9; }
|
||||
.email-wrapper { max-width: 600px; margin: 0 auto; background-color: #ffffff; }
|
||||
.email-header { background: linear-gradient(135deg, #206bc4 0%, #4299e1 100%); padding: 30px 40px; text-align: center; }
|
||||
.email-header h1 { color: #ffffff; font-size: 24px; font-weight: 600; margin: 0; }
|
||||
.email-header .subtitle { color: rgba(255,255,255,0.9); font-size: 14px; margin-top: 5px; }
|
||||
.email-body { padding: 40px; background-color: #ffffff; }
|
||||
.email-body h2 { color: #206bc4; font-size: 20px; font-weight: 600; margin-bottom: 20px; border-bottom: 2px solid #e9ecef; padding-bottom: 10px; }
|
||||
.email-body p { margin-bottom: 15px; color: #495057; }
|
||||
.email-body .highlight { background-color: #e7f3ff; border-left: 4px solid #206bc4; padding: 15px 20px; margin: 20px 0; border-radius: 0 4px 4px 0; }
|
||||
.email-body ul { margin: 15px 0; padding-left: 0; list-style: none; }
|
||||
.email-body ul li { padding: 10px 15px; border-bottom: 1px solid #e9ecef; background-color: #f8f9fa; }
|
||||
.email-body ul li:first-child { border-radius: 4px 4px 0 0; }
|
||||
.email-body ul li:last-child { border-bottom: none; border-radius: 0 0 4px 4px; }
|
||||
.email-body ul li strong { color: #206bc4; min-width: 100px; display: inline-block; }
|
||||
.btn { display: inline-block; padding: 12px 30px; background: linear-gradient(135deg, #206bc4 0%, #4299e1 100%); color: #ffffff !important; text-decoration: none; border-radius: 6px; font-weight: 500; margin: 10px 5px 10px 0; }
|
||||
.email-footer { background-color: #f8f9fa; padding: 30px 40px; text-align: center; border-top: 1px solid #e9ecef; }
|
||||
.email-footer .signature { color: #6c757d; font-size: 13px; line-height: 1.8; }
|
||||
.email-footer .company-info { margin-top: 15px; padding-top: 15px; border-top: 1px solid #e9ecef; color: #adb5bd; font-size: 12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="email-wrapper">
|
||||
<div class="email-header">
|
||||
<h1>{{ guest.system_company.name }}</h1>
|
||||
<div class="subtitle">LXD容器已暂停</div>
|
||||
</div>
|
||||
<div class="email-body">
|
||||
<h2>服务已暂停</h2>
|
||||
<p>您好 {{ c.first_name }} {{ c.last_name }},</p>
|
||||
|
||||
<div class="highlight">
|
||||
<p><strong>注意:</strong>您的LXD容器已被暂停。这通常是由于账单逾期未支付导致的。</p>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<li><strong>容器名称:</strong> {{ service.container_name }}</li>
|
||||
<li><strong>服务名称:</strong> {{ order.title }}</li>
|
||||
</ul>
|
||||
|
||||
<p>请尽快登录客户中心处理未付账单,以恢复您的容器服务。</p>
|
||||
|
||||
<p style="text-align: center; margin: 30px 0;">
|
||||
<a href="{{ 'login'|link({'email': c.email}) }}" class="btn">登录客户中心</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="email-footer">
|
||||
<div class="signature">{{ guest.system_company.signature }}</div>
|
||||
<div class="company-info">此邮件由 {{ guest.system_company.name }} 系统自动发送</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
|
||||
{# ========== 汉化邮箱插件 by xkatld 结束 ========== #}
|
||||
@@ -1,68 +0,0 @@
|
||||
{# ========== 汉化邮箱插件 by xkatld 开始 ========== #}
|
||||
{# @link https://github.com/xkatld/FOSSBilling-Patch #}
|
||||
{# @version v1.0.0 #}
|
||||
|
||||
{% block subject %}[{{ guest.system_company.name }}] {{ order.title }} 已恢复{% endblock %}
|
||||
{% block content %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style type="text/css">
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; font-size: 14px; line-height: 1.6; color: #333; background-color: #f4f6f9; }
|
||||
.email-wrapper { max-width: 600px; margin: 0 auto; background-color: #ffffff; }
|
||||
.email-header { background: linear-gradient(135deg, #206bc4 0%, #4299e1 100%); padding: 30px 40px; text-align: center; }
|
||||
.email-header h1 { color: #ffffff; font-size: 24px; font-weight: 600; margin: 0; }
|
||||
.email-header .subtitle { color: rgba(255,255,255,0.9); font-size: 14px; margin-top: 5px; }
|
||||
.email-body { padding: 40px; background-color: #ffffff; }
|
||||
.email-body h2 { color: #206bc4; font-size: 20px; font-weight: 600; margin-bottom: 20px; border-bottom: 2px solid #e9ecef; padding-bottom: 10px; }
|
||||
.email-body p { margin-bottom: 15px; color: #495057; }
|
||||
.email-body .highlight { background-color: #e7f3ff; border-left: 4px solid #206bc4; padding: 15px 20px; margin: 20px 0; border-radius: 0 4px 4px 0; }
|
||||
.email-body ul { margin: 15px 0; padding-left: 0; list-style: none; }
|
||||
.email-body ul li { padding: 10px 15px; border-bottom: 1px solid #e9ecef; background-color: #f8f9fa; }
|
||||
.email-body ul li:first-child { border-radius: 4px 4px 0 0; }
|
||||
.email-body ul li:last-child { border-bottom: none; border-radius: 0 0 4px 4px; }
|
||||
.email-body ul li strong { color: #206bc4; min-width: 100px; display: inline-block; }
|
||||
.btn { display: inline-block; padding: 12px 30px; background: linear-gradient(135deg, #206bc4 0%, #4299e1 100%); color: #ffffff !important; text-decoration: none; border-radius: 6px; font-weight: 500; margin: 10px 5px 10px 0; }
|
||||
.email-footer { background-color: #f8f9fa; padding: 30px 40px; text-align: center; border-top: 1px solid #e9ecef; }
|
||||
.email-footer .signature { color: #6c757d; font-size: 13px; line-height: 1.8; }
|
||||
.email-footer .company-info { margin-top: 15px; padding-top: 15px; border-top: 1px solid #e9ecef; color: #adb5bd; font-size: 12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="email-wrapper">
|
||||
<div class="email-header">
|
||||
<h1>{{ guest.system_company.name }}</h1>
|
||||
<div class="subtitle">LXD容器已恢复</div>
|
||||
</div>
|
||||
<div class="email-body">
|
||||
<h2>服务已恢复</h2>
|
||||
<p>您好 {{ c.first_name }} {{ c.last_name }},</p>
|
||||
|
||||
<div class="highlight">
|
||||
<p><strong>好消息!</strong>您的LXD容器已恢复正常运行。</p>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<li><strong>容器名称:</strong> {{ service.container_name }}</li>
|
||||
<li><strong>服务名称:</strong> {{ order.title }}</li>
|
||||
</ul>
|
||||
|
||||
<p>您现在可以继续使用容器服务了。</p>
|
||||
|
||||
<p style="text-align: center; margin: 30px 0;">
|
||||
<a href="{{ 'order/service/manage'|link }}/{{ order.id }}" class="btn">管理容器</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="email-footer">
|
||||
<div class="signature">{{ guest.system_company.signature }}</div>
|
||||
<div class="company-info">此邮件由 {{ guest.system_company.name }} 系统自动发送</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
|
||||
{# ========== 汉化邮箱插件 by xkatld 结束 ========== #}
|
||||
Reference in New Issue
Block a user