重构:整理项目结构,删除冗余文件并调整目录组织

This commit is contained in:
勿忘心安
2025-12-29 11:25:40 +00:00
parent 183181034c
commit 1820db2850
40 changed files with 1135 additions and 829 deletions

View File

@@ -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>

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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"
}

View 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,
];
}
}

View 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>");
}
}

View File

@@ -0,0 +1,7 @@
<?php
return array(
'key'=>'swapidc_lxdapi',
'name'=>'SWAPIDC-LXD对接插件 by xkatld',
'version'=>'2.0.2'
);
?>

View File

@@ -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',

View File

@@ -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',
];
}

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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 结束 ========== #}

View File

@@ -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 结束 ========== #}

View File

@@ -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 结束 ========== #}

View File

@@ -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 结束 ========== #}