Delete scripts
Browse files- scripts/pending.py +0 -66
- scripts/start-openclaw.sh +0 -70
- scripts/sync.py +0 -122
- scripts/telegram-dns-check.sh +0 -98
scripts/pending.py
DELETED
|
@@ -1,66 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
import subprocess
|
| 3 |
-
import json
|
| 4 |
-
import sys
|
| 5 |
-
import time
|
| 6 |
-
|
| 7 |
-
def run_command(cmd):
|
| 8 |
-
try:
|
| 9 |
-
result = subprocess.run(
|
| 10 |
-
cmd,
|
| 11 |
-
shell=True,
|
| 12 |
-
capture_output=True,
|
| 13 |
-
text=True,
|
| 14 |
-
check=True,
|
| 15 |
-
timeout=10 # 增加超时防止卡死
|
| 16 |
-
)
|
| 17 |
-
return result.stdout.strip()
|
| 18 |
-
except subprocess.CalledProcessError as e:
|
| 19 |
-
# 如果是非零退出码,可能是没有 pending 请求,静默处理
|
| 20 |
-
return None
|
| 21 |
-
except subprocess.TimeoutExpired:
|
| 22 |
-
print("--- [Pending] 命令执行超时 ---")
|
| 23 |
-
return None
|
| 24 |
-
|
| 25 |
-
def main():
|
| 26 |
-
print("--- [Pending]: 检查设备待批准列表...")
|
| 27 |
-
|
| 28 |
-
json_output = run_command("openclaw devices list --json")
|
| 29 |
-
|
| 30 |
-
if not json_output:
|
| 31 |
-
# 可能是还没初始化,或者命令本身不需要输出
|
| 32 |
-
sys.exit(0)
|
| 33 |
-
|
| 34 |
-
try:
|
| 35 |
-
data = json.loads(json_output)
|
| 36 |
-
except json.JSONDecodeError:
|
| 37 |
-
# 输出不是有效的 JSON,忽略
|
| 38 |
-
sys.exit(0)
|
| 39 |
-
|
| 40 |
-
pending_list = data.get("pending", [])
|
| 41 |
-
|
| 42 |
-
if not isinstance(pending_list, list):
|
| 43 |
-
sys.exit(0)
|
| 44 |
-
|
| 45 |
-
request_ids = []
|
| 46 |
-
|
| 47 |
-
# 逻辑优化:统一遍历逻辑,不再硬编码索引切片,除非数据结构非常特殊
|
| 48 |
-
# 根据原逻辑,似乎是从索引 3 开始或者是字典类型
|
| 49 |
-
for item in pending_list:
|
| 50 |
-
if isinstance(item, dict) and "requestId" in item:
|
| 51 |
-
request_ids.append(item["requestId"])
|
| 52 |
-
|
| 53 |
-
if not request_ids:
|
| 54 |
-
sys.exit(0)
|
| 55 |
-
|
| 56 |
-
print(f"--- [Pending] 发现 {len(request_ids)} 个待批准请求 ---")
|
| 57 |
-
|
| 58 |
-
for req_id in request_ids:
|
| 59 |
-
cmd = f"openclaw devices approve {req_id}"
|
| 60 |
-
print(f"--- [Pending] 正在批准: {req_id} ---")
|
| 61 |
-
subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
| 62 |
-
|
| 63 |
-
print("--- [Pending] 所有请求处理完毕 ---")
|
| 64 |
-
|
| 65 |
-
if __name__ == "__main__":
|
| 66 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scripts/start-openclaw.sh
DELETED
|
@@ -1,70 +0,0 @@
|
|
| 1 |
-
#!/bin/bash
|
| 2 |
-
set -e
|
| 3 |
-
|
| 4 |
-
# 捕获信号以优雅关闭
|
| 5 |
-
trap "echo '收到终止信号,正在关闭后台任务...'; kill -TERM '$CHILD_PID' 2>/dev/null; exit 0" SIGTERM SIGINT
|
| 6 |
-
|
| 7 |
-
echo "--- [Start] 初始化 OpenClaw 环境 ---"
|
| 8 |
-
|
| 9 |
-
# 1. 数据恢复
|
| 10 |
-
echo "--- [Start] 执行数据恢复 ---"
|
| 11 |
-
python3 /usr/local/bin/sync.py restore
|
| 12 |
-
|
| 13 |
-
# 2. 验证配置json
|
| 14 |
-
echo "--- [Start] 验证配置openclaw.json ---"
|
| 15 |
-
if command -v jq &> /dev/null; then
|
| 16 |
-
if ! jq empty "/root/.openclaw/openclaw.json" 2>/dev/null; then
|
| 17 |
-
echo "警告: 配置的 openclaw.json 可能格式有误,请检查配置。"
|
| 18 |
-
cat "/root/.openclaw/openclaw.json"
|
| 19 |
-
else
|
| 20 |
-
echo "配置文件格式有效。"
|
| 21 |
-
fi
|
| 22 |
-
fi
|
| 23 |
-
|
| 24 |
-
# 3. 更新
|
| 25 |
-
echo "--- [Start] 检查更新 ---"
|
| 26 |
-
openclaw update --yes --no-restart
|
| 27 |
-
|
| 28 |
-
# 4. 启动后台任务
|
| 29 |
-
echo "--- [Start] 启动后台守护进程 ---"
|
| 30 |
-
|
| 31 |
-
{
|
| 32 |
-
sleep 10
|
| 33 |
-
# 后台单独执行 Telegram DNS判断
|
| 34 |
-
/usr/local/bin/telegram-dns-check.sh
|
| 35 |
-
} &
|
| 36 |
-
|
| 37 |
-
(
|
| 38 |
-
sleep 60
|
| 39 |
-
# 任务 A: 自动批准 (每 10 分钟 = 600 秒)
|
| 40 |
-
while true; do
|
| 41 |
-
echo "[Cron] 执行设备自动批准..."
|
| 42 |
-
python3 /usr/local/bin/pending.py
|
| 43 |
-
sleep 600
|
| 44 |
-
done
|
| 45 |
-
) &
|
| 46 |
-
PID_APPROVE=$!
|
| 47 |
-
|
| 48 |
-
(
|
| 49 |
-
# 任务 B: 数据备份 (每 10 小时 = 36000 秒)
|
| 50 |
-
# 先等待一小会儿再开始第一次备份,避免启动瞬间占用资源
|
| 51 |
-
sleep 610
|
| 52 |
-
while true; do
|
| 53 |
-
echo "[Cron] 执行数据备份..."
|
| 54 |
-
python3 /usr/local/bin/sync.py backup
|
| 55 |
-
sleep 36000
|
| 56 |
-
done
|
| 57 |
-
) &
|
| 58 |
-
PID_BACKUP=$!
|
| 59 |
-
|
| 60 |
-
# 记录子进程 ID 用于信号处理
|
| 61 |
-
CHILD_PID="$PID_APPROVE $PID_BACKUP"
|
| 62 |
-
|
| 63 |
-
# 5. 运行诊断修复
|
| 64 |
-
#echo "--- [Start] 运行 Doctor 修复 ---"
|
| 65 |
-
#openclaw doctor --fix || echo "--- [Warning] Doctor 修复完成但有警告 ---"
|
| 66 |
-
|
| 67 |
-
# 6. 启动主服务
|
| 68 |
-
echo "--- [Start] 启动 OpenClaw Gateway (端口: ${PORT}) ---"
|
| 69 |
-
# exec 替换当前 shell,使 openclaw 成为 PID 1,便于接收信号
|
| 70 |
-
exec openclaw gateway run --port ${PORT}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scripts/sync.py
DELETED
|
@@ -1,122 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
import os
|
| 3 |
-
import sys
|
| 4 |
-
import tarfile
|
| 5 |
-
from huggingface_hub import HfApi, hf_hub_download
|
| 6 |
-
|
| 7 |
-
# 配置常量
|
| 8 |
-
BACKUP_DIR = "/root/.openclaw"
|
| 9 |
-
BACKUP_FILENAME = "backup.tar.gz"
|
| 10 |
-
|
| 11 |
-
def restore():
|
| 12 |
-
repo_id = os.getenv("HF_DATASET")
|
| 13 |
-
token = os.getenv("HF_TOKEN")
|
| 14 |
-
|
| 15 |
-
if not repo_id or not token:
|
| 16 |
-
print("--- [SYNC] 跳过恢复: 未配置 HF_DATASET 或 HF_TOKEN ---")
|
| 17 |
-
return True # 返回 True 表示流程继续,只是没恢复数据
|
| 18 |
-
|
| 19 |
-
try:
|
| 20 |
-
print(f"--- [SYNC] 启动恢复流程, 目标仓库: {repo_id} ---")
|
| 21 |
-
api = HfApi()
|
| 22 |
-
|
| 23 |
-
# 获取文件列表
|
| 24 |
-
files = api.list_repo_files(repo_id=repo_id, repo_type="dataset", token=token)
|
| 25 |
-
|
| 26 |
-
if BACKUP_FILENAME in files:
|
| 27 |
-
print(f"--- [SYNC] 发现备份文件: {BACKUP_FILENAME}, 正在下载... ---")
|
| 28 |
-
# 下载到本地临时位置
|
| 29 |
-
downloaded_path = hf_hub_download(
|
| 30 |
-
repo_id=repo_id,
|
| 31 |
-
filename=BACKUP_FILENAME,
|
| 32 |
-
repo_type="dataset",
|
| 33 |
-
token=token,
|
| 34 |
-
force_download=True # 确保每次都是最新的
|
| 35 |
-
)
|
| 36 |
-
|
| 37 |
-
extract_path = BACKUP_DIR
|
| 38 |
-
print(f"--- [SYNC] 正在解压至 {extract_path} ... ---")
|
| 39 |
-
|
| 40 |
-
with tarfile.open(downloaded_path, "r:gz") as tar:
|
| 41 |
-
# 安全解压逻辑 (兼容旧版本 Python)
|
| 42 |
-
def is_within_directory(directory, target):
|
| 43 |
-
abs_directory = os.path.abspath(directory)
|
| 44 |
-
abs_target = os.path.abspath(target)
|
| 45 |
-
prefix = os.path.commonprefix([abs_directory, abs_target])
|
| 46 |
-
return prefix == abs_directory
|
| 47 |
-
|
| 48 |
-
for member in tar.getmembers():
|
| 49 |
-
member_path = os.path.join(extract_path, member.name)
|
| 50 |
-
if not is_within_directory(extract_path, member_path):
|
| 51 |
-
raise Exception(f"尝试跳出目录的攻击行为: {member.name}")
|
| 52 |
-
|
| 53 |
-
tar.extractall(path=extract_path)
|
| 54 |
-
|
| 55 |
-
print(f"--- [SYNC] 恢复成功! 数据已覆盖至 {extract_path} ---")
|
| 56 |
-
return True
|
| 57 |
-
|
| 58 |
-
else:
|
| 59 |
-
# 修正日志:不再谎称检查了时间
|
| 60 |
-
print("--- [SYNC] 未在仓库中找到备份文件,将使用空状态启动 ---")
|
| 61 |
-
return True
|
| 62 |
-
except Exception as e:
|
| 63 |
-
print(f"--- [SYNC] 恢复异常: {e} ---")
|
| 64 |
-
import traceback
|
| 65 |
-
traceback.print_exc()
|
| 66 |
-
return True # 即使失败也返回 True,避免阻塞启动
|
| 67 |
-
|
| 68 |
-
def backup():
|
| 69 |
-
repo_id = os.getenv("HF_DATASET")
|
| 70 |
-
token = os.getenv("HF_TOKEN")
|
| 71 |
-
|
| 72 |
-
if not repo_id or not token:
|
| 73 |
-
print("--- [SYNC] 跳过备份: 未配置 HF_DATASET 或 HF_TOKEN ---")
|
| 74 |
-
return
|
| 75 |
-
|
| 76 |
-
try:
|
| 77 |
-
print(f"--- [SYNC] 正在执行全量备份: {BACKUP_FILENAME} ---")
|
| 78 |
-
|
| 79 |
-
base_path = BACKUP_DIR
|
| 80 |
-
if not os.path.exists(base_path):
|
| 81 |
-
print(f"--- [SYNC] 警告: 备份目录 {base_path} 不存在,跳过打包 ---")
|
| 82 |
-
# 即使目录不存在,也可以上传一个空的标记文件或者直接返回,这里选择返回
|
| 83 |
-
return
|
| 84 |
-
|
| 85 |
-
# 创建临时压缩包
|
| 86 |
-
with tarfile.open(BACKUP_FILENAME, "w:gz") as tar:
|
| 87 |
-
# 修复点:将 arcname 设置为 '.',这样解压后直接是 .openclaw 的内容,
|
| 88 |
-
# 或者设置为 os.path.basename(base_path) 保留文件夹名。
|
| 89 |
-
# 通常为了还原方便,我们希望在 .openclaw 目录下直接看到内容,所以用 '.'
|
| 90 |
-
# 如果希望解压出来是 /root/.openclaw/.openclaw/... 则用 basename
|
| 91 |
-
arcname = "."
|
| 92 |
-
tar.add(base_path, arcname=arcname)
|
| 93 |
-
print(f"--- [SYNC] 本地打包完成,大小: {os.path.getsize(BACKUP_FILENAME) / 1024 / 1024:.2f} MB ---")
|
| 94 |
-
|
| 95 |
-
api = HfApi()
|
| 96 |
-
print(f"--- [SYNC] 正在上传至 Hugging Face ({repo_id}) ... ---")
|
| 97 |
-
|
| 98 |
-
api.upload_file(
|
| 99 |
-
path_or_fileobj=BACKUP_FILENAME,
|
| 100 |
-
path_in_repo=BACKUP_FILENAME,
|
| 101 |
-
repo_id=repo_id,
|
| 102 |
-
repo_type="dataset",
|
| 103 |
-
token=token
|
| 104 |
-
)
|
| 105 |
-
|
| 106 |
-
print(f"--- [SYNC] 备份上传成功! ---")
|
| 107 |
-
|
| 108 |
-
# 清理本地临时备份文件以节省空间
|
| 109 |
-
if os.path.exists(BACKUP_FILENAME):
|
| 110 |
-
os.remove(BACKUP_FILENAME)
|
| 111 |
-
print("--- [SYNC] 本地临时文件已清理 ---")
|
| 112 |
-
|
| 113 |
-
except Exception as e:
|
| 114 |
-
print(f"--- [SYNC] 备份失败: {e} ---")
|
| 115 |
-
import traceback
|
| 116 |
-
traceback.print_exc()
|
| 117 |
-
|
| 118 |
-
if __name__ == "__main__":
|
| 119 |
-
if len(sys.argv) > 1 and sys.argv[1] == "backup":
|
| 120 |
-
backup()
|
| 121 |
-
else:
|
| 122 |
-
restore()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scripts/telegram-dns-check.sh
DELETED
|
@@ -1,98 +0,0 @@
|
|
| 1 |
-
#!/bin/bash
|
| 2 |
-
# Telegram DNS 检查脚本
|
| 3 |
-
# 每小时检查 Telegram API 连接,如果 DNS 解析失败则更新 hosts
|
| 4 |
-
|
| 5 |
-
TELEGRAM_DOMAIN="api.telegram.org"
|
| 6 |
-
HOSTS_FILE="/etc/hosts"
|
| 7 |
-
DNS_SERVER="8.8.8.8"
|
| 8 |
-
|
| 9 |
-
# 检查 Telegram API 是否可以解析
|
| 10 |
-
check_telegram() {
|
| 11 |
-
# 尝试解析域名
|
| 12 |
-
timeout 10 curl -sI "https://api.telegram.org" > /dev/null 2>&1
|
| 13 |
-
return $?
|
| 14 |
-
}
|
| 15 |
-
|
| 16 |
-
# 使用 DNS 服务器解析域名
|
| 17 |
-
resolve_telegram() {
|
| 18 |
-
python3 -c "
|
| 19 |
-
import socket
|
| 20 |
-
import struct
|
| 21 |
-
|
| 22 |
-
def dns_query(domain, dns_server='8.8.8.8'):
|
| 23 |
-
query = b'\x01\x00'
|
| 24 |
-
query += b'\x01\x00'
|
| 25 |
-
query += b'\x00\x01\x00\x00\x00\x00\x00\x00'
|
| 26 |
-
|
| 27 |
-
for part in domain.split('.'):
|
| 28 |
-
query += bytes([len(part)]) + part.encode()
|
| 29 |
-
query += b'\x00'
|
| 30 |
-
query += b'\x00\x01\x00\x01'
|
| 31 |
-
|
| 32 |
-
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
| 33 |
-
sock.settimeout(5)
|
| 34 |
-
sock.sendto(query, (dns_server, 53))
|
| 35 |
-
|
| 36 |
-
response, _ = sock.recvfrom(512)
|
| 37 |
-
sock.close()
|
| 38 |
-
|
| 39 |
-
pos = 12
|
| 40 |
-
while response[pos] != 0:
|
| 41 |
-
pos += response[pos] + 1
|
| 42 |
-
pos += 5
|
| 43 |
-
|
| 44 |
-
ancount = struct.unpack('!H', response[6:8])[0]
|
| 45 |
-
|
| 46 |
-
for _ in range(ancount):
|
| 47 |
-
if response[pos] >= 0xc0:
|
| 48 |
-
pos += 2
|
| 49 |
-
else:
|
| 50 |
-
while response[pos] != 0:
|
| 51 |
-
pos += response[pos] + 1
|
| 52 |
-
pos += 1
|
| 53 |
-
rtype = struct.unpack('!H', response[pos:pos+2])[0]
|
| 54 |
-
pos += 8
|
| 55 |
-
rdlength = struct.unpack('!H', response[pos:pos+2])[0]
|
| 56 |
-
pos += 2
|
| 57 |
-
if rtype == 1 and rdlength == 4:
|
| 58 |
-
ip = '.'.join(str(b) for b in response[pos:pos+4])
|
| 59 |
-
return ip
|
| 60 |
-
pos += rdlength
|
| 61 |
-
return None
|
| 62 |
-
|
| 63 |
-
ip = dns_query('api.telegram.org', '8.8.8.8')
|
| 64 |
-
if ip:
|
| 65 |
-
print(ip)
|
| 66 |
-
" 2>/dev/null
|
| 67 |
-
}
|
| 68 |
-
|
| 69 |
-
# 更新 hosts 文件
|
| 70 |
-
update_hosts() {
|
| 71 |
-
local ip="$1"
|
| 72 |
-
|
| 73 |
-
# 检查是否已存在记录
|
| 74 |
-
if grep -q "$TELEGRAM_DOMAIN" "$HOSTS_FILE"; then
|
| 75 |
-
# 替换现有记录
|
| 76 |
-
sed -i "s/^[0-9.]*[[:space:]]*$TELEGRAM_DOMAIN/$ip $TELEGRAM_DOMAIN/" "$HOSTS_FILE"
|
| 77 |
-
echo "[$(date)] Updated $TELEGRAM_DOMAIN -> $ip in $HOSTS_FILE"
|
| 78 |
-
else
|
| 79 |
-
# 添加新记录
|
| 80 |
-
echo "$ip $TELEGRAM_DOMAIN" >> "$HOSTS_FILE"
|
| 81 |
-
echo "[$(date)] Added $TELEGRAM_DOMAIN -> $ip to $HOSTS_FILE"
|
| 82 |
-
fi
|
| 83 |
-
}
|
| 84 |
-
|
| 85 |
-
# 主逻辑
|
| 86 |
-
if ! check_telegram; then
|
| 87 |
-
echo "[$(date)] Telegram API connection failed, resolving via $DNS_SERVER..."
|
| 88 |
-
ip=$(resolve_telegram)
|
| 89 |
-
if [ -n "$ip" ]; then
|
| 90 |
-
update_hosts "$ip"
|
| 91 |
-
echo "[$(date)] Telegram DNS updated successfully: $TELEGRAM_DOMAIN -> $ip"
|
| 92 |
-
else
|
| 93 |
-
echo "[$(date)] Failed to resolve $TELEGRAM_DOMAIN via $DNS_SERVER"
|
| 94 |
-
exit 1
|
| 95 |
-
fi
|
| 96 |
-
else
|
| 97 |
-
echo "[$(date)] Telegram API connection OK"
|
| 98 |
-
fi
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|