Files
dashboard/backend/ota_storage.py
T
yangy 1564c8766d Update environment variable loading to allow overrides
- Modified load_dotenv() calls in multiple files to include override=True, ensuring that .env variables can replace existing environment variables.
- Updated ota_storage.py to use default values for OTA upload directories, improving clarity and maintainability.
- Enhanced get_ota_upload_dir() function to read environment variables dynamically, ensuring accurate directory paths based on the current environment.
2026-05-18 09:56:58 +08:00

125 lines
4.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""OTA 升级包存储:Luxsin-X8 -> S3Luxsin-X9 -> 本地目录"""
import hashlib
import io
import logging
import os
from datetime import datetime
from pathlib import Path
import boto3
from botocore.exceptions import ClientError
from fastapi import UploadFile
logger = logging.getLogger(__name__)
OTA_MODEL_X8 = "Luxsin-X8"
OTA_MODEL_X9 = "Luxsin-X9"
OTA_UPLOAD_MODELS = {OTA_MODEL_X8, OTA_MODEL_X9}
# 默认本地根目录;实际路径为 {根}/{md5前5位}/LUXSIN.PKG(具体目录在 get_ota_upload_dir 内按当前环境读取)
_OTA_UPLOAD_DIR_DEV_DEFAULT = "H:/soft/projects/luxsin/dashboard/ota"
_OTA_UPLOAD_DIR_PROD_DEFAULT = "/data/project/dashboard/upload"
AWS_REGION = os.getenv("AWS_REGION", "eu-central-1")
AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID", "")
AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY", "")
AWS_S3_OTA_BUCKET = os.getenv("AWS_S3_OTA_BUCKET", "luxsin-app-bucket")
OTA_X8_PUBLIC_BASE = (os.getenv("OTA_X8_PUBLIC_BASE") or "http://am.luxsinaudio.com").rstrip(
"/"
)
OTA_X9_URL_BASE = (os.getenv("OTA_X9_URL_BASE") or "http://source.luxsin.net").rstrip("/")
OTA_FILENAME_X8 = "LUXSIN_X8.PKG"
OTA_FILENAME_X9 = "LUXSIN.PKG"
def get_ota_upload_dir() -> Path:
"""按 DEBUG 选开发/生产目录;每次调用重新读环境变量,避免 import 早于 load_dotenv。"""
debug_val = (os.getenv("DEBUG") or "false").strip().lower()
is_dev = debug_val in ("true", "1", "yes", "on")
dev = os.getenv("OTA_UPLOAD_DIR_DEV", _OTA_UPLOAD_DIR_DEV_DEFAULT)
prod = os.getenv("OTA_UPLOAD_DIR_PROD", _OTA_UPLOAD_DIR_PROD_DEFAULT)
raw = dev if is_dev else prod
return Path(raw)
def md5_prefix5(md5_hex: str) -> str:
return (md5_hex or "")[:5]
def build_x8_public_url(s3_key: str) -> str:
"""X8http://am.luxsinaudio.com/ota/{yyyyMM}/x8/{md5前5位}/LUXSIN_X8.PKG"""
return f"{OTA_X8_PUBLIC_BASE}/{s3_key}"
def build_x9_public_url(ym: str, prefix: str) -> str:
"""X9http://source.luxsin.net/ota/{yyyyMM}/x9/{md5前5位}/LUXSIN.PKG"""
return f"{OTA_X9_URL_BASE}/ota/{ym}/x9/{prefix}/{OTA_FILENAME_X9}"
async def read_upload_content_and_md5(package_file: UploadFile) -> tuple[bytes, str]:
md5_hash = hashlib.md5()
buf = io.BytesIO()
chunk_size = 1024 * 1024
while True:
chunk = await package_file.read(chunk_size)
if not chunk:
break
md5_hash.update(chunk)
buf.write(chunk)
return buf.getvalue(), md5_hash.hexdigest()
def save_x9_package_local(content: bytes, md5_hex: str) -> tuple[str, str]:
"""
保存到 {OTA_UPLOAD_DIR}/{md5前5位}/LUXSIN.PKG
对外 URLhttp://source.luxsin.net/ota/{yyyyMM}/x9/{md5前5位}/LUXSIN.PKG
"""
root = get_ota_upload_dir()
prefix = md5_prefix5(md5_hex)
ym = datetime.now().strftime("%Y%m")
dest_dir = root / prefix
dest_dir.mkdir(parents=True, exist_ok=True)
final_path = dest_dir / OTA_FILENAME_X9
if final_path.exists():
final_path.unlink()
final_path.write_bytes(content)
download_url = build_x9_public_url(ym, prefix)
logger.info("OTA X9 package saved locally: %s", final_path)
return OTA_FILENAME_X9, download_url
def upload_x8_package_to_s3(content: bytes, md5_hex: str) -> tuple[str, str, str]:
"""
S3 Keyota/{yyyyMM}/x8/{md5前5位}/LUXSIN_X8.PKG
对外 URLhttp://am.luxsinaudio.com/ota/{yyyyMM}/x8/{md5前5位}/LUXSIN_X8.PKG
"""
if not AWS_ACCESS_KEY_ID or not AWS_SECRET_ACCESS_KEY:
raise ValueError("未配置 AWS 访问密钥(AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY")
ym = datetime.now().strftime("%Y%m")
prefix = md5_prefix5(md5_hex)
s3_key = f"ota/{ym}/x8/{prefix}/{OTA_FILENAME_X8}"
client = boto3.client(
"s3",
region_name=AWS_REGION,
aws_access_key_id=AWS_ACCESS_KEY_ID,
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
)
try:
client.put_object(
Bucket=AWS_S3_OTA_BUCKET,
Key=s3_key,
Body=content,
ContentType="application/octet-stream",
)
except ClientError as e:
logger.error("S3 upload failed: %s", e, exc_info=True)
raise ValueError(f"S3 上传失败:{e}") from e
download_url = build_x8_public_url(s3_key)
logger.info("OTA X8 package uploaded to s3://%s/%s", AWS_S3_OTA_BUCKET, s3_key)
return OTA_FILENAME_X8, download_url, s3_key